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
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Install npm
|
||||
|
@ -142,16 +142,16 @@ npm run dist
|
|||
log_message "Installing monkey requirements"
|
||||
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
||||
cd ${monkey_home}/monkey/infection_monkey || handle_error
|
||||
python -m pip install --user -r requirements.txt || handle_error
|
||||
python -m pip install --user -r requirements_linux.txt || handle_error
|
||||
|
||||
# Build samba
|
||||
log_message "Building samba binaries"
|
||||
sudo apt-get install gcc-multilib
|
||||
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
|
||||
chmod +x ./build.sh || handle_error
|
||||
sudo chmod +x ./build.sh || handle_error
|
||||
./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."
|
||||
exit 0
|
|
@ -86,7 +86,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
|
|||
}
|
||||
& python -m pip install --user -r $islandRequirements
|
||||
# Install requirements for monkey
|
||||
$monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt"
|
||||
$monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements_windows.txt"
|
||||
& python -m pip install --user -r $monkeyRequirements
|
||||
|
||||
# Download mongodb
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.md
|
|
@ -1,19 +1,24 @@
|
|||
FROM debian:jessie-slim
|
||||
FROM debian:stretch-slim
|
||||
|
||||
LABEL MAINTAINER="theonlydoo <theonlydoo@gmail.com>"
|
||||
|
||||
ARG RELEASE=1.6
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
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 upgrade \
|
||||
&& apt-get -yqq install python-pip \
|
||||
libssl-dev \
|
||||
supervisor \
|
||||
&& dpkg -i *.deb
|
||||
python-dev \
|
||||
&& dpkg -i *.deb \
|
||||
&& rm -f *.deb *.tgz
|
||||
|
||||
COPY stack.conf /etc/supervisor/conf.d/stack.conf
|
||||
|
||||
ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ]
|
||||
WORKDIR /var/monkey
|
||||
ENTRYPOINT ["/var/monkey/monkey_island/bin/python/bin/python"]
|
||||
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'
|
||||
|
||||
|
||||
class AWS(object):
|
||||
class AwsInstance(object):
|
||||
def __init__(self):
|
||||
try:
|
||||
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",
|
||||
"Struts2Exploiter",
|
||||
"WebLogicExploiter",
|
||||
"HadoopExploiter",
|
||||
"MSSQLExploiter"
|
||||
"HadoopExploiter"
|
||||
],
|
||||
"finger_classes": [
|
||||
"SSHFinger",
|
||||
|
|
|
@ -17,13 +17,14 @@ class MSSQLExploiter(HostExploiter):
|
|||
_TARGET_OS_TYPE = ['windows']
|
||||
LOGIN_TIMEOUT = 15
|
||||
SQL_DEFAULT_TCP_PORT = '1433'
|
||||
DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat'
|
||||
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat')
|
||||
DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat'
|
||||
|
||||
def __init__(self, host):
|
||||
super(MSSQLExploiter, self).__init__(host)
|
||||
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
|
||||
|
||||
def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH):
|
||||
def create_payload_file(self, payload_path):
|
||||
"""
|
||||
This function creates dynamically the payload file to be transported and ran on the exploited machine.
|
||||
:param payload_path: A path to the create the payload file in
|
||||
|
@ -45,10 +46,13 @@ class MSSQLExploiter(HostExploiter):
|
|||
"""
|
||||
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
|
||||
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))
|
||||
return True
|
||||
else:
|
||||
|
|
|
@ -7,6 +7,7 @@ from io import BytesIO
|
|||
from os import path
|
||||
|
||||
import impacket.smbconnection
|
||||
from impacket.nmb import NetBIOSError
|
||||
from impacket.nt_errors import STATUS_SUCCESS
|
||||
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
|
||||
|
@ -172,7 +173,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
if self.is_share_writable(smb_client, share):
|
||||
writable_shares_creds_dict[share] = credentials
|
||||
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
except (impacket.smbconnection.SessionError, SessionError, NetBIOSError):
|
||||
# If failed using some credentials, try others.
|
||||
pass
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ SERVER_TIMEOUT = 4
|
|||
# How long should be wait after each request in seconds
|
||||
REQUEST_DELAY = 0.0001
|
||||
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
|
||||
REQUEST_TIMEOUT = 2
|
||||
REQUEST_TIMEOUT = 5
|
||||
# How long to wait for response in exploitation. In seconds
|
||||
EXECUTION_TIMEOUT = 15
|
||||
URLS = ["/wls-wsat/CoordinatorPortType",
|
||||
|
|
|
@ -69,7 +69,6 @@ def process_datas(orig_datas):
|
|||
def get_binaries():
|
||||
binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries()
|
||||
binaries += get_sc_binaries()
|
||||
binaries += get_traceroute_binaries()
|
||||
return binaries
|
||||
|
||||
|
||||
|
@ -81,6 +80,7 @@ def get_windows_only_binaries():
|
|||
|
||||
def get_linux_only_binaries():
|
||||
binaries = []
|
||||
binaries += get_traceroute_binaries()
|
||||
return binaries
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ class BackdoorUser(object):
|
|||
|
||||
def act(self):
|
||||
LOG.info("Adding a user")
|
||||
try:
|
||||
if sys.platform.startswith("win"):
|
||||
retval = self.add_user_windows()
|
||||
else:
|
||||
|
@ -31,6 +32,8 @@ class BackdoorUser(object):
|
|||
LOG.warn("Failed to add a user")
|
||||
else:
|
||||
LOG.info("Done adding user")
|
||||
except OSError:
|
||||
LOG.exception("Exception while adding a user")
|
||||
|
||||
@staticmethod
|
||||
def add_user_linux():
|
||||
|
|
|
@ -5,36 +5,30 @@ The monkey is composed of three separate parts.
|
|||
* The Infection Monkey itself - PyInstaller compressed python archives
|
||||
* Sambacry binaries - Two linux binaries, 32/64 bit.
|
||||
* Mimikatz binaries - Two windows binaries, 32/64 bit.
|
||||
* Traceroute binaries - Two linux binaries, 32/64bit.
|
||||
|
||||
--- Windows ---
|
||||
|
||||
1. Install python 2.7. Preferably you should use ActiveState Python which includes pywin32 built in.
|
||||
You must use an up to date version, at least version 2.7.10
|
||||
https://www.python.org/download/releases/2.7/
|
||||
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)
|
||||
1. Install python 2.7.15
|
||||
Download and install from: https://www.python.org/downloads/release/python-2715/
|
||||
2. 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)
|
||||
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.
|
||||
4. Install pip
|
||||
a. Download and run the pip installer
|
||||
https://bootstrap.pypa.io/get-pip.py
|
||||
5. Install further dependencies
|
||||
3. Install further dependencies
|
||||
a. install VCForPython27.msi
|
||||
https://aka.ms/vcpython27
|
||||
b. if not installed, install Microsoft Visual C++ 2010 SP1 Redistributable Package
|
||||
32bit: http://www.microsoft.com/en-us/download/details.aspx?id=8328
|
||||
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
|
||||
6. Download the dependent python packages using
|
||||
pip install -r requirements.txt
|
||||
7. Download and extract UPX binary to [source-path]\monkey\infection_monkey\bin\upx.exe:
|
||||
4. Download the dependent python packages using
|
||||
pip install -r requirements_windows.txt
|
||||
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
|
||||
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.
|
||||
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
|
||||
build_windows.bat
|
||||
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
|
||||
Install the python packages listed in requirements.txt using pip
|
||||
cd [code location]/infection_monkey
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements_linux.txt
|
||||
2. Build Sambacry binaries
|
||||
a. Build/Download according to sections at the end of this readme.
|
||||
b. Place the binaries under [code location]\infection_monkey\bin
|
||||
3. To build, run in terminal:
|
||||
b. Place the binaries under [code location]\infection_monkey\bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
|
||||
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
|
||||
chmod +x build_linux.sh
|
||||
./build_linux.sh
|
||||
|
@ -61,19 +58,45 @@ Tested on Ubuntu 16.04 and 17.04.
|
|||
-- Sambacry --
|
||||
|
||||
Sambacry requires two standalone binaries to execute remotely.
|
||||
1. Install gcc-multilib if it's not installed
|
||||
a. Build sambacry binaries yourself
|
||||
a.1. Install gcc-multilib if it's not installed
|
||||
sudo apt-get install gcc-multilib
|
||||
2. Build the binaries
|
||||
a.2. Build the binaries
|
||||
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 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
|
||||
Download both 32 and 64 bit zipped DLLs and place them under [code location]\infection_monkey\bin
|
||||
Alternatively, if you build Mimikatz, put each version in a zip file.
|
||||
1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll
|
||||
2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'.
|
||||
3. The zip file should be named mk32.zip/mk64.zip accordingly.
|
||||
4. Zipping with 7zip has been tested. Other zipping software may not work.
|
||||
You can either build them yourself or download pre-built binaries.
|
||||
a. Build Mimikatz yourself
|
||||
a.0. Building mimikatz requires Visual Studio 2013 and up
|
||||
a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0
|
||||
a.2. Build using Visual Studio.
|
||||
a.3. Put each version in a zip file
|
||||
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
|
||||
impacket
|
||||
PyCrypto
|
||||
pycryptodome
|
||||
pyasn1
|
||||
cffi
|
||||
twisted
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
|
||||
from common.cloud.aws import AWS
|
||||
from common.cloud.aws_instance import AwsInstance
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
@ -15,7 +15,7 @@ class AwsCollector(object):
|
|||
@staticmethod
|
||||
def get_aws_info():
|
||||
LOG.info("Collecting AWS info")
|
||||
aws = AWS()
|
||||
aws = AwsInstance()
|
||||
info = {}
|
||||
if aws.is_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.netmap import NetMap
|
||||
from cc.resources.node import Node
|
||||
from cc.resources.remote_run import RemoteRun
|
||||
from cc.resources.report import Report
|
||||
from cc.resources.root import Root
|
||||
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(Log, '/api/log', '/api/log/')
|
||||
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
|
||||
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||
|
||||
return app
|
||||
|
|
|
@ -33,7 +33,6 @@ def init_jwt(app):
|
|||
user_id = payload['identity']
|
||||
return userid_table.get(user_id, None)
|
||||
|
||||
if env.is_auth_enabled():
|
||||
JWT(app, authenticate, identity)
|
||||
|
||||
|
||||
|
@ -41,12 +40,11 @@ def jwt_required(realm=None):
|
|||
def wrapper(fn):
|
||||
@wraps(fn)
|
||||
def decorator(*args, **kwargs):
|
||||
if env.is_auth_enabled():
|
||||
try:
|
||||
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
|
||||
return fn(*args, **kwargs)
|
||||
except JWTError:
|
||||
abort(401)
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class Encryptor:
|
|||
def enc(self, message):
|
||||
cipher_iv = Random.new().read(AES.block_size)
|
||||
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):
|
||||
enc_message = base64.b64decode(enc_message)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import abc
|
||||
from datetime import timedelta
|
||||
import os
|
||||
from Crypto.Hash import SHA3_512
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
@ -13,6 +14,12 @@ class Environment(object):
|
|||
_DEBUG_SERVER = False
|
||||
_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):
|
||||
return self._ISLAND_PORT
|
||||
|
||||
|
@ -25,9 +32,10 @@ class Environment(object):
|
|||
def get_auth_expiration_time(self):
|
||||
return self._AUTH_EXPIRATION_TIME
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_auth_enabled(self):
|
||||
return
|
||||
def hash_secret(self, secret):
|
||||
h = SHA3_512.new()
|
||||
h.update(secret)
|
||||
return h.hexdigest()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_users(self):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import cc.auth
|
||||
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'
|
||||
|
||||
|
@ -8,7 +9,7 @@ __author__ = 'itay.mizeretz'
|
|||
class AwsEnvironment(Environment):
|
||||
def __init__(self):
|
||||
super(AwsEnvironment, self).__init__()
|
||||
self.aws_info = AWS()
|
||||
self.aws_info = AwsInstance()
|
||||
self._instance_id = self._get_instance_id()
|
||||
self.region = self._get_region()
|
||||
|
||||
|
@ -18,10 +19,7 @@ class AwsEnvironment(Environment):
|
|||
def _get_region(self):
|
||||
return self.aws_info.get_region()
|
||||
|
||||
def is_auth_enabled(self):
|
||||
return True
|
||||
|
||||
def get_auth_users(self):
|
||||
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 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__)
|
||||
|
||||
AWS = 'aws'
|
||||
STANDARD = 'standard'
|
||||
PASSWORD = 'password'
|
||||
|
||||
ENV_DICT = {
|
||||
'standard': standard.StandardEnvironment,
|
||||
'aws': aws.AwsEnvironment
|
||||
STANDARD: standard.StandardEnvironment,
|
||||
AWS: aws.AwsEnvironment,
|
||||
PASSWORD: password.PasswordEnvironment,
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,8 +31,10 @@ def load_env_from_file():
|
|||
return config_json['server_config']
|
||||
|
||||
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.set_config(config_json)
|
||||
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
|
||||
except Exception:
|
||||
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
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
|
||||
class StandardEnvironment(Environment):
|
||||
|
||||
def is_auth_enabled(self):
|
||||
return False
|
||||
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
|
||||
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
|
||||
|
||||
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.services.config import ConfigService
|
||||
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'
|
||||
|
||||
|
@ -23,7 +23,7 @@ class AWSExporter(Exporter):
|
|||
|
||||
@staticmethod
|
||||
def handle_report(report_json):
|
||||
aws = AWS()
|
||||
aws = AwsInstance()
|
||||
findings_list = []
|
||||
issues_list = report_json['recommendations']['issues']
|
||||
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
|
||||
def get_displayed_telemetry(telem):
|
||||
monkey = NodeService.get_monkey_by_guid(telem['monkey_guid'])
|
||||
default_hostname = "GUID-" + telem['monkey_guid']
|
||||
return \
|
||||
{
|
||||
'id': telem['_id'],
|
||||
'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)
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ class TelemetryFeed(flask_restful.Resource):
|
|||
@staticmethod
|
||||
def get_state_telem_brief(telem):
|
||||
if telem['data']['done']:
|
||||
return 'Monkey died.'
|
||||
return '''Monkey finishing it's execution.'''
|
||||
else:
|
||||
return 'Monkey started.'
|
||||
|
||||
|
|
|
@ -304,7 +304,6 @@ SCHEMA = {
|
|||
"$ref": "#/definitions/post_breach_acts"
|
||||
},
|
||||
"default": [
|
||||
"BackdoorUser",
|
||||
],
|
||||
"description": "List of actions the Monkey will run post breach"
|
||||
},
|
||||
|
@ -689,7 +688,6 @@ SCHEMA = {
|
|||
"default": [
|
||||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"MSSQLExploiter",
|
||||
"SSHExploiter",
|
||||
"ShellShockExploiter",
|
||||
"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-table": "^6.8.6",
|
||||
"react-toggle": "^4.0.1",
|
||||
"redux": "^4.0.0"
|
||||
"redux": "^4.0.0",
|
||||
"sha3": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,15 @@ let guardicoreLogoImage = require('../images/guardicore-logo.png');
|
|||
|
||||
class AppComponent extends AuthComponent {
|
||||
updateStatus = () => {
|
||||
if (this.auth.loggedIn()){
|
||||
this.auth.loggedIn()
|
||||
.then(res => {
|
||||
if (this.state.isLoggedIn !== res) {
|
||||
this.setState({
|
||||
isLoggedIn: res
|
||||
});
|
||||
}
|
||||
|
||||
if (res) {
|
||||
this.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
|
@ -44,14 +52,19 @@ class AppComponent extends AuthComponent {
|
|||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
||||
let render_func = (props) => {
|
||||
if (this.auth.loggedIn()) {
|
||||
switch (this.state.isLoggedIn) {
|
||||
case true:
|
||||
return page_component;
|
||||
} else {
|
||||
case false:
|
||||
return <Redirect to={{pathname: '/login'}}/>;
|
||||
default:
|
||||
return page_component;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -69,7 +82,8 @@ class AppComponent extends AuthComponent {
|
|||
run_server: true,
|
||||
run_monkey: false,
|
||||
infection_done: false,
|
||||
report_done: false
|
||||
report_done: false,
|
||||
isLoggedIn: undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
resetConfig = () => {
|
||||
this.authFetch('/api/configuration',
|
||||
this.authFetch('/api/configuration/island',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
|
@ -141,9 +141,12 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
.then(res => res.json())
|
||||
.then(res => {
|
||||
// This check is used to prevent unnecessary re-rendering
|
||||
let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']);
|
||||
if (allMonkeysAreDead !== this.state.allMonkeysAreDead) {
|
||||
this.setState({
|
||||
allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done'])
|
||||
allMonkeysAreDead: allMonkeysAreDead
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -34,9 +34,12 @@ class LoginPageComponent extends React.Component {
|
|||
this.state = {
|
||||
failed: false
|
||||
};
|
||||
if (this.auth.loggedIn()) {
|
||||
this.auth.loggedIn()
|
||||
.then(res => {
|
||||
if (res) {
|
||||
this.redirectToHome();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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 {Icon} from 'react-fa';
|
||||
import {Link} from 'react-router-dom';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import AwsRunTable from "../run-monkey/AwsRunTable";
|
||||
|
||||
class RunMonkeyPageComponent extends AuthComponent {
|
||||
|
||||
|
@ -13,9 +14,18 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
ips: [],
|
||||
runningOnIslandState: 'not_running',
|
||||
runningOnClientState: 'not_running',
|
||||
awsClicked: false,
|
||||
selectedIp: '0.0.0.0',
|
||||
selectedOs: 'windows-32',
|
||||
showManual: false
|
||||
showManual: false,
|
||||
showAws: false,
|
||||
isOnAws: false,
|
||||
isAwsAuth: false,
|
||||
awsUpdateClicked: false,
|
||||
awsUpdateFailed: false,
|
||||
awsKeyId: '',
|
||||
awsSecretKey: '',
|
||||
awsMachines: []
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
|
@ -50,6 +69,17 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
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) {
|
||||
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`
|
||||
|
@ -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() {
|
||||
return (
|
||||
<Col xs={12} lg={8}>
|
||||
|
@ -166,7 +382,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
<p className="text-center">
|
||||
OR
|
||||
</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' : '')}>
|
||||
Run on machine of your choice
|
||||
</button>
|
||||
|
@ -196,6 +412,30 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
{this.generateCmdDiv()}
|
||||
</div>
|
||||
</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'}}>
|
||||
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 React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'babel-polyfill';
|
||||
import App from './components/Main';
|
||||
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 AwsConfig from './AwsConfig';
|
||||
import PasswordConfig from "./PasswordConfig";
|
||||
|
||||
const SERVER_CONFIG_JSON = require('../../../server_config.json');
|
||||
|
||||
const CONFIG_DICT =
|
||||
{
|
||||
'standard': StandardConfig,
|
||||
'aws': AwsConfig
|
||||
'aws': AwsConfig,
|
||||
'password': PasswordConfig
|
||||
};
|
||||
|
||||
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 {SERVER_CONFIG} from '../server_config/ServerConfig';
|
||||
|
||||
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) => {
|
||||
if (this.AUTH_ENABLED) {
|
||||
return this._login(username, password);
|
||||
} else {
|
||||
return {result: true};
|
||||
}
|
||||
return this._login(username, this.hashSha3(password));
|
||||
};
|
||||
|
||||
authFetch = (url, options) => {
|
||||
if (this.AUTH_ENABLED) {
|
||||
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) => {
|
||||
return this._authFetch('/api/auth', {
|
||||
method: 'POST',
|
||||
|
@ -36,7 +37,6 @@ export default class AuthService {
|
|||
this._removeToken();
|
||||
return {result: false};
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@ export default class AuthService {
|
|||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
if (this.loggedIn()) {
|
||||
if (this._loggedIn()) {
|
||||
headers['Authorization'] = 'JWT ' + this._getToken();
|
||||
}
|
||||
|
||||
|
@ -67,20 +67,26 @@ export default class AuthService {
|
|||
});
|
||||
};
|
||||
|
||||
loggedIn() {
|
||||
if (!this.AUTH_ENABLED) {
|
||||
return true;
|
||||
async loggedIn() {
|
||||
let token = this._getToken();
|
||||
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();
|
||||
return ((token !== null) && !this._isTokenExpired(token));
|
||||
}
|
||||
|
||||
logout() {
|
||||
if (this.AUTH_ENABLED) {
|
||||
logout = () => {
|
||||
this._removeToken();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_isTokenExpired(token) {
|
||||
try {
|
||||
|
@ -103,4 +109,9 @@ export default class AuthService {
|
|||
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
|
||||
Version: 1.0
|
||||
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/
|
||||
chmod +x ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/start_server.sh
|
||||
systemctl daemon-reload
|
||||
systemctl enable monkey-mongo
|
||||
systemctl enable monkey-island
|
||||
fi
|
||||
|
||||
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
|
||||
|
||||
service monkey-island start
|
||||
service monkey-mongo start
|
||||
|
||||
echo Monkey Island installation ended
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
service monkey-island stop || true
|
||||
service monkey-mongo stop || true
|
||||
|
||||
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-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service
|
||||
|
||||
rm -r -f /var/monkey
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ jsonschema
|
|||
netifaces
|
||||
ipaddress
|
||||
enum34
|
||||
PyCrypto
|
||||
pycryptodome
|
||||
boto3
|
||||
awscli
|
||||
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
|
||||
|
||||
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
|
|
@ -8,7 +8,7 @@ respawn limit unlimited
|
|||
|
||||
script
|
||||
chdir /var/monkey
|
||||
exec python monkey_island/cc/main.py
|
||||
exec monkey_island/bin/python/bin/python monkey_island.py
|
||||
end 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]
|
||||
Description=Monkey Island Service
|
||||
Wants=monkey-mongo.service
|
||||
After=network.target
|
||||
|
||||
[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,25 +6,22 @@ How to set up the Monkey Island server:
|
|||
---------------- On Windows ----------------:
|
||||
0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation.
|
||||
1. Create folder "bin" under monkey_island
|
||||
2. Place portable version of Python 2.7
|
||||
2.1. Download and install from: https://www.python.org/download/releases/2.7/
|
||||
2.2. Install the required python libraries using "python -m pip install -r monkey_island\requirements.txt"
|
||||
2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
|
||||
2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
|
||||
2.5. (Optional) You may uninstall Python27 if you like.
|
||||
2. Place portable version of Python 2.7.15
|
||||
2.1. Download and install from: https://www.python.org/downloads/release/python-2715/
|
||||
2.2. Install virtualenv using "python -m pip install virtualenv"
|
||||
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. Run "python -m virtualenv --relocatable <PATH TO BIN>\Python27"
|
||||
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.1 Place portable version of mongodb
|
||||
3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
|
||||
3.2.1 Extract contents from bin folder to monkey_island\bin\mongodb.
|
||||
3.3.1 Create monkey_island\db folder.
|
||||
|
||||
3.a Place portable version of mongodb
|
||||
3.a.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
|
||||
3.a.2. Extract contents from bin folder to monkey_island\bin\mongodb.
|
||||
3.a.3. Create monkey_island\db folder.
|
||||
OR
|
||||
|
||||
3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
||||
|
||||
example for mongodb running on host with IP address 192.168.10.10:
|
||||
|
||||
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
|
||||
3.b. Use already running instance of mongodb
|
||||
3.b.1. Run 'set MONKEY_MONGO_URL="mongodb://<SERVER ADDR>:27017/monkeyisland"'. Replace '<SERVER ADDR>' with address of mongo server
|
||||
|
||||
4. Place portable version of OpenSSL
|
||||
4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip
|
||||
|
@ -67,23 +64,16 @@ How to run:
|
|||
monkey-windows-64.exe - monkey binary for windows 64bi
|
||||
|
||||
4. Setup MongoDB (Use one of the two following options):
|
||||
|
||||
4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb
|
||||
4.a. 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 ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
|
||||
find more at - https://www.mongodb.org/downloads#production
|
||||
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb
|
||||
(make sure the content of the mongo folder is in this directory, meaning this path exists:
|
||||
/var/monkey_island/bin/mongodb/bin)
|
||||
|
||||
OR
|
||||
|
||||
4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
||||
|
||||
example for mongodb running on host with IP address 192.168.10.10:
|
||||
|
||||
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
|
||||
|
||||
4.b. Use already running instance of mongodb
|
||||
4.b.1. Run 'set MONKEY_MONGO_URL="mongodb://<SERVER ADDR>:27017/monkeyisland"'. Replace '<SERVER ADDR>' with address of mongo server
|
||||
|
||||
5. install OpenSSL
|
||||
sudo apt-get install openssl
|
||||
|
|
|
@ -13,6 +13,6 @@ jsonschema
|
|||
netifaces
|
||||
ipaddress
|
||||
enum34
|
||||
PyCrypto
|
||||
pycryptodome
|
||||
boto3
|
||||
awscli
|
|
@ -1,4 +1,4 @@
|
|||
@title C^&C Server
|
||||
@pushd ..
|
||||
@monkey_island\bin\Python27\python monkey_island.py
|
||||
@monkey_island\bin\Python27\Scripts\python monkey_island.py
|
||||
@popd
|
Loading…
Reference in New Issue