Merge branch 'develop'

This commit is contained in:
itay 2019-02-26 12:38:05 +02:00
commit 38381c4c9d
64 changed files with 4262 additions and 3310 deletions

View File

@ -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

View File

@ -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

1
docker/.dockerignore Normal file
View File

@ -0,0 +1 @@
*.md

View File

@ -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 \
&& apt-get -yqq update \
&& apt-get -yqq upgrade \
&& apt-get -yqq install python-pip \
libssl-dev \
supervisor \
&& dpkg -i *.deb
RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \
&& apt-get -yqq update \
&& apt-get -yqq upgrade \
&& apt-get -yqq install python-pip \
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"]

22
docker/docker-compose.yml Normal file
View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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']
]

View File

View File

View File

@ -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'))

View File

@ -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']

11
monkey/common/cmd/cmd.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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)))

View File

@ -0,0 +1,9 @@
from enum import Enum
__author__ = 'itay.mizeretz'
class CmdStatus(Enum):
IN_PROGRESS = 0
SUCCESS = 1
FAILURE = 2

View File

@ -41,8 +41,7 @@
"SambaCryExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"MSSQLExploiter"
"HadoopExploiter"
],
"finger_classes": [
"SSHFinger",

View File

@ -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:

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -23,14 +23,17 @@ class BackdoorUser(object):
def act(self):
LOG.info("Adding a user")
if sys.platform.startswith("win"):
retval = self.add_user_windows()
else:
retval = self.add_user_linux()
if retval != 0:
LOG.warn("Failed to add a user")
else:
LOG.info("Done adding user")
try:
if sys.platform.startswith("win"):
retval = self.add_user_windows()
else:
retval = self.add_user_linux()
if retval != 0:
LOG.warn("Failed to add a user")
else:
LOG.info("Done adding user")
except OSError:
LOG.exception("Exception while adding a user")
@staticmethod
def add_user_linux():

View File

@ -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
sudo apt-get install gcc-multilib
2. Build the binaries
cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
./build.sh
a. Build sambacry binaries yourself
a.1. Install gcc-multilib if it's not installed
sudo apt-get install gcc-multilib
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

View File

@ -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

View File

@ -1,6 +1,6 @@
enum34
impacket
PyCrypto
pycryptodome
pyasn1
cffi
twisted

View File

@ -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")

View File

@ -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

View File

@ -33,20 +33,18 @@ def init_jwt(app):
user_id = payload['identity']
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 wrapper(fn):
@wraps(fn)
def decorator(*args, **kwargs):
if env.is_auth_enabled():
try:
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
except JWTError:
abort(401)
return fn(*args, **kwargs)
try:
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
return fn(*args, **kwargs)
except JWTError:
abort(401)
return decorator

View File

@ -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)

View File

@ -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):

View File

@ -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))
]

View File

@ -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)

View File

@ -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'])
]

View File

@ -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)
]

View File

@ -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:

View File

@ -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)

View File

@ -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.'

View File

@ -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",

View File

@ -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

View File

@ -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"
}
}

View File

@ -27,31 +27,44 @@ let guardicoreLogoImage = require('../images/guardicore-logo.png');
class AppComponent extends AuthComponent {
updateStatus = () => {
if (this.auth.loggedIn()){
this.authFetch('/api')
.then(res => res.json())
.then(res => {
// This check is used to prevent unnecessary re-rendering
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']});
}
});
}
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 => {
// This check is used to prevent unnecessary re-rendering
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) => {
let render_func = (props) => {
if (this.auth.loggedIn()) {
return page_component;
} else {
return <Redirect to={{pathname: '/login'}}/>;
switch (this.state.isLoggedIn) {
case true:
return page_component;
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
}
};
}

View File

@ -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
this.setState({
allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done'])
});
let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']);
if (allMonkeysAreDead !== this.state.allMonkeysAreDead) {
this.setState({
allMonkeysAreDead: allMonkeysAreDead
});
}
});
};

View File

@ -34,9 +34,12 @@ class LoginPageComponent extends React.Component {
this.state = {
failed: false
};
if (this.auth.loggedIn()) {
this.redirectToHome();
}
this.auth.loggedIn()
.then(res => {
if (res) {
this.redirectToHome();
}
});
}
render() {

View File

@ -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,10 +14,19 @@ 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: []
};
}
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')
.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.

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
import BaseConfig from './BaseConfig';
class PasswordConfig extends BaseConfig{
isAuthEnabled() {
return true;
}
}
export default PasswordConfig;

View File

@ -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']]();

View File

@ -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);
}
return this._authFetch(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) {
this._removeToken();
}
}
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)), '');
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -13,7 +13,7 @@ jsonschema
netifaces
ipaddress
enum34
PyCrypto
pycryptodome
boto3
awscli
virtualenv

View File

@ -1,6 +0,0 @@
#!/bin/bash
service monkey-mongo stop
cd /var/monkey/monkey_island
rm -rf ./db/*
service monkey-mongo start

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,5 @@
[Unit]
Description=Monkey Island Service
Wants=monkey-mongo.service
After=network.target
[Service]

View File

@ -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

View File

@ -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

View File

@ -13,6 +13,6 @@ jsonschema
netifaces
ipaddress
enum34
PyCrypto
pycryptodome
boto3
awscli

View File

@ -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