Merge pull request #83 from guardicore/develop

Develop
This commit is contained in:
itaymmguardicore 2018-01-20 20:55:10 +02:00 committed by GitHub
commit 7f5734ac67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2267 additions and 174 deletions

View File

@ -38,8 +38,10 @@ class ShellShockExploiter(HostExploiter):
def exploit_host(self): def exploit_host(self):
# start by picking ports # start by picking ports
candidate_services = {service: self.host.services[service] for service in self.host.services if candidate_services = {
self.host.services[service]['name'] == 'http'} service: self.host.services[service] for service in self.host.services if
('name' in self.host.services[service]) and (self.host.services[service]['name'] == 'http')
}
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if
'tcp-' + str(port) in candidate_services] 'tcp-' + str(port) in candidate_services]

View File

@ -1,14 +1,17 @@
import os from __future__ import print_function
import sys
import logging
import traceback
import logging.config
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
from model import MONKEY_ARG, DROPPER_ARG
from dropper import MonkeyDrops
from monkey import ChaosMonkey
import argparse import argparse
import json import json
import logging
import logging.config
import os
import sys
import traceback
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
from dropper import MonkeyDrops
from model import MONKEY_ARG, DROPPER_ARG
from monkey import ChaosMonkey
if __name__ == "__main__": if __name__ == "__main__":
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -55,22 +58,22 @@ def main():
config_file = opts.config config_file = opts.config
if os.path.isfile(config_file): if os.path.isfile(config_file):
# using print because config can also change log locations # using print because config can also change log locations
print "Loading config from %s." % config_file print("Loading config from %s." % config_file)
try: try:
with open(config_file) as config_fo: with open(config_file) as config_fo:
json_dict = json.load(config_fo) json_dict = json.load(config_fo)
WormConfiguration.from_dict(json_dict) WormConfiguration.from_dict(json_dict)
except ValueError as e: except ValueError as e:
print "Error loading config: %s, using default" % (e,) print("Error loading config: %s, using default" % (e,))
else: else:
print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,)) print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
print "Loaded Configuration: %r" % WormConfiguration.as_dict() print("Loaded Configuration: %r" % WormConfiguration.as_dict())
# Make sure we're not in a machine that has the kill file # Make sure we're not in a machine that has the kill file
kill_path = os.path.expandvars(WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux kill_path = os.path.expandvars(WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux
if os.path.exists(kill_path): if os.path.exists(kill_path):
print "Kill path found, finished run" print("Kill path found, finished run")
return True return True
try: try:

View File

@ -1,3 +1,4 @@
import logging
import socket import socket
import sys import sys
@ -6,6 +7,8 @@ from enum import IntEnum
from network.info import get_host_subnets from network.info import get_host_subnets
LOG = logging.getLogger(__name__)
# Linux doesn't have WindowsError # Linux doesn't have WindowsError
try: try:
WindowsError WindowsError
@ -56,8 +59,9 @@ class InfoCollector(object):
def get_hostname(self): def get_hostname(self):
""" """
Adds the fully qualified computer hostname to the system information. Adds the fully qualified computer hostname to the system information.
:return: Nothing :return: None. Updates class information
""" """
LOG.debug("Reading hostname")
self.info['hostname'] = socket.getfqdn() self.info['hostname'] = socket.getfqdn()
def get_process_list(self): def get_process_list(self):
@ -65,8 +69,9 @@ class InfoCollector(object):
Adds process information from the host to the system information. Adds process information from the host to the system information.
Currently lists process name, ID, parent ID, command line Currently lists process name, ID, parent ID, command line
and the full image path of each process. and the full image path of each process.
:return: Nothing :return: None. Updates class information
""" """
LOG.debug("Reading process list")
processes = {} processes = {}
for process in psutil.process_iter(): for process in psutil.process_iter():
try: try:
@ -95,6 +100,7 @@ class InfoCollector(object):
Adds network information from the host to the system information. Adds network information from the host to the system information.
Currently updates with a list of networks accessible from host, Currently updates with a list of networks accessible from host,
containing host ip and the subnet range. containing host ip and the subnet range.
:return: None :return: None. Updates class information
""" """
LOG.debug("Reading subnets")
self.info['network_info'] = {'networks': get_host_subnets()} self.info['network_info'] = {'networks': get_host_subnets()}

View File

@ -1,7 +1,11 @@
import logging
from . import InfoCollector from . import InfoCollector
__author__ = 'uri' __author__ = 'uri'
LOG = logging.getLogger(__name__)
class LinuxInfoCollector(InfoCollector): class LinuxInfoCollector(InfoCollector):
""" """
@ -12,6 +16,12 @@ class LinuxInfoCollector(InfoCollector):
super(LinuxInfoCollector, self).__init__() super(LinuxInfoCollector, self).__init__()
def get_info(self): def get_info(self):
"""
Collect Linux system information
Hostname, process list and network subnets
:return: Dict of system information
"""
LOG.debug("Running Linux collector")
self.get_hostname() self.get_hostname()
self.get_process_list() self.get_process_list()
self.get_network_info() self.get_network_info()

View File

@ -1,5 +1,5 @@
import ctypes
import binascii import binascii
import ctypes
import logging import logging
import socket import socket
@ -8,13 +8,14 @@ __author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class MimikatzCollector: class MimikatzCollector(object):
""" """
Password collection module for Windows using Mimikatz. Password collection module for Windows using Mimikatz.
""" """
def __init__(self): def __init__(self):
try: try:
self._isInit = False self._isInit = False
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name) self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name)
@ -31,9 +32,9 @@ class MimikatzCollector:
Gets the logon info from mimikatz. Gets the logon info from mimikatz.
Returns a dictionary of users with their known credentials. Returns a dictionary of users with their known credentials.
""" """
if not self._isInit: if not self._isInit:
return {} return {}
LOG.debug("Running mimikatz collector")
try: try:
entry_count = self._collect() entry_count = self._collect()

View File

@ -1,5 +1,10 @@
from . import InfoCollector import logging
from mimikatz_collector import MimikatzCollector from mimikatz_collector import MimikatzCollector
from . import InfoCollector
LOG = logging.getLogger(__name__)
__author__ = 'uri' __author__ = 'uri'
@ -12,6 +17,13 @@ class WindowsInfoCollector(InfoCollector):
super(WindowsInfoCollector, self).__init__() super(WindowsInfoCollector, self).__init__()
def get_info(self): def get_info(self):
"""
Collect Windows system information
Hostname, process list and network subnets
Tries to read credential secrets using mimikatz
:return: Dict of system information
"""
LOG.debug("Running Windows collector")
self.get_hostname() self.get_hostname()
self.get_process_list() self.get_process_list()
self.get_network_info() self.get_network_info()

View File

@ -15,8 +15,9 @@ from cc.resources.monkey_download import MonkeyDownload
from cc.resources.netmap import NetMap from cc.resources.netmap import NetMap
from cc.resources.edge import Edge from cc.resources.edge import Edge
from cc.resources.node import Node from cc.resources.node import Node
from cc.resources.report import Report
from cc.resources.root import Root from cc.resources.root import Root
from cc.resources.telemetry_feed import TelemetryFeed
from cc.services.config import ConfigService from cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'
@ -88,5 +89,7 @@ def init_app(mongo_url):
api.add_resource(NetMap, '/api/netmap', '/api/netmap/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
api.add_resource(Report, '/api/report', '/api/report/')
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
return app return app

View File

@ -1,4 +1,5 @@
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
ISLAND_PORT = 5000 ISLAND_PORT = 5000
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland" DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
DEBUG_SERVER = False

View File

@ -11,7 +11,7 @@ if BASE_PATH not in sys.path:
from cc.app import init_app from cc.app import init_app
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT, DEBUG_SERVER
from cc.database import is_db_server_up from cc.database import is_db_server_up
if __name__ == '__main__': if __name__ == '__main__':
@ -26,11 +26,13 @@ if __name__ == '__main__':
time.sleep(1) time.sleep(1)
app = init_app(mongo_url) app = init_app(mongo_url)
if DEBUG_SERVER:
app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))
else:
http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
http_server.listen(ISLAND_PORT)
print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT))
IOLoop.instance().start()
http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
http_server.listen(ISLAND_PORT)
print('Monkey Island C&C Server is running on https://{}:{}'.format(local_ip_addresses()[0], ISLAND_PORT))
IOLoop.instance().start()
# app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))

View File

@ -36,7 +36,7 @@ def run_local_monkey():
# run the monkey # run the monkey
try: try:
args = ["%s m0nk3y -s %s:%s" % (target_path, local_ip_addresses()[0], ISLAND_PORT)] args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], ISLAND_PORT)]
if sys.platform == "win32": if sys.platform == "win32":
args = "".join(args) args = "".join(args)
pid = subprocess.Popen(args, shell=True).pid pid = subprocess.Popen(args, shell=True).pid

View File

@ -53,6 +53,8 @@ class Monkey(flask_restful.Resource):
def post(self, **kw): def post(self, **kw):
monkey_json = json.loads(request.data) monkey_json = json.loads(request.data)
monkey_json['creds'] = []
monkey_json['dead'] = False
if 'keepalive' in monkey_json: if 'keepalive' in monkey_json:
monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
else: else:
@ -60,6 +62,8 @@ class Monkey(flask_restful.Resource):
monkey_json['modifytime'] = datetime.now() monkey_json['modifytime'] = datetime.now()
ConfigService.save_initial_config_if_needed()
# if new monkey telem, change config according to "new monkeys" config. # if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
if not db_monkey: if not db_monkey:
@ -119,6 +123,8 @@ class Monkey(flask_restful.Resource):
node_id = existing_node["_id"] node_id = existing_node["_id"]
for edge in mongo.db.edge.find({"to": node_id}): for edge in mongo.db.edge.find({"to": node_id}):
mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}}) mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}})
for creds in existing_node['creds']:
NodeService.add_credentials_to_monkey(new_monkey_id, creds)
mongo.db.node.remove({"_id": node_id}) mongo.db.node.remove({"_id": node_id})
return {"id": new_monkey_id} return {"id": new_monkey_id}

View File

@ -0,0 +1,10 @@
import flask_restful
from cc.services.report import ReportService
__author__ = "itay.mizeretz"
class Report(flask_restful.Resource):
def get(self):
return ReportService.get_report()

View File

@ -1,12 +1,12 @@
from datetime import datetime from datetime import datetime
from flask import request, make_response, jsonify
import flask_restful import flask_restful
from flask import request, make_response, jsonify
from cc.database import mongo from cc.database import mongo
from cc.services.config import ConfigService from cc.services.config import ConfigService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.services.report import ReportService
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
__author__ = 'Barak' __author__ = 'Barak'
@ -18,24 +18,35 @@ class Root(flask_restful.Resource):
action = request.args.get('action') action = request.args.get('action')
if not action: if not action:
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), completed_steps=self.get_completed_steps()) return Root.get_server_info()
elif action == "reset": elif action == "reset":
mongo.db.config.drop() return Root.reset_db()
mongo.db.monkey.drop()
mongo.db.telemetry.drop()
mongo.db.node.drop()
mongo.db.edge.drop()
ConfigService.init_config()
return jsonify(status='OK')
elif action == "killall": elif action == "killall":
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, return Root.kill_all()
multi=True)
return jsonify(status='OK')
else: else:
return make_response(400, {'error': 'unknown action'}) return make_response(400, {'error': 'unknown action'})
def get_completed_steps(self): @staticmethod
def get_server_info():
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
completed_steps=Root.get_completed_steps())
@staticmethod
def reset_db():
[mongo.db[x].drop() for x in ['config', 'monkey', 'telemetry', 'node', 'edge', 'report']]
ConfigService.init_config()
return jsonify(status='OK')
@staticmethod
def kill_all():
mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}},
upsert=False,
multi=True)
return jsonify(status='OK')
@staticmethod
def get_completed_steps():
is_any_exists = NodeService.is_any_monkey_exists() is_any_exists = NodeService.is_any_monkey_exists()
is_any_alive = NodeService.is_any_monkey_alive() infection_done = NodeService.is_monkey_finished_running()
return dict(run_server=True, run_monkey=is_any_exists, infection_done=(is_any_exists and not is_any_alive)) report_done = ReportService.is_report_generated()
return dict(run_server=True, run_monkey=is_any_exists, infection_done=infection_done, report_done=report_done)

View File

@ -1,5 +1,6 @@
import json import json
import traceback import traceback
import copy
from datetime import datetime from datetime import datetime
import dateutil import dateutil
@ -39,7 +40,6 @@ class Telemetry(flask_restful.Resource):
telemetry_json = json.loads(request.data) telemetry_json = json.loads(request.data)
telemetry_json['timestamp'] = datetime.now() telemetry_json['timestamp'] = datetime.now()
telem_id = mongo.db.telemetry.insert(telemetry_json)
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
try: try:
@ -53,6 +53,7 @@ class Telemetry(flask_restful.Resource):
print("Exception caught while processing telemetry: %s" % str(ex)) print("Exception caught while processing telemetry: %s" % str(ex))
traceback.print_exc() traceback.print_exc()
telem_id = mongo.db.telemetry.insert(telemetry_json)
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
@staticmethod @staticmethod
@ -70,6 +71,11 @@ class Telemetry(flask_restful.Resource):
monkey_label = telem_monkey_guid monkey_label = telem_monkey_guid
x["monkey"] = monkey_label x["monkey"] = monkey_label
objects.append(x) objects.append(x)
if x['telem_type'] == 'system_info_collection' and 'credentials' in x['data']:
for user in x['data']['credentials']:
if -1 != user.find(','):
new_user = user.replace(',', '.')
x['data']['credentials'][new_user] = x['data']['credentials'].pop(user)
return objects return objects
@ -103,7 +109,7 @@ class Telemetry(flask_restful.Resource):
@staticmethod @staticmethod
def process_exploit_telemetry(telemetry_json): def process_exploit_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
new_exploit = telemetry_json['data'] new_exploit = copy.deepcopy(telemetry_json['data'])
new_exploit.pop('machine') new_exploit.pop('machine')
new_exploit['timestamp'] = telemetry_json['timestamp'] new_exploit['timestamp'] = telemetry_json['timestamp']
@ -115,10 +121,18 @@ class Telemetry(flask_restful.Resource):
if new_exploit['result']: if new_exploit['result']:
EdgeService.set_edge_exploited(edge) EdgeService.set_edge_exploited(edge)
for attempt in telemetry_json['data']['attempts']:
if attempt['result']:
found_creds = {'user': attempt['user']}
for field in ['password', 'lm_hash', 'ntlm_hash']:
if len(attempt[field]) != 0:
found_creds[field] = attempt[field]
NodeService.add_credentials_to_node(edge['to'], found_creds)
@staticmethod @staticmethod
def process_scan_telemetry(telemetry_json): def process_scan_telemetry(telemetry_json):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data = telemetry_json['data']['machine'] data = copy.deepcopy(telemetry_json['data']['machine'])
ip_address = data.pop("ip_addr") ip_address = data.pop("ip_addr")
new_scan = \ new_scan = \
{ {
@ -158,11 +172,17 @@ class Telemetry(flask_restful.Resource):
if 'ntlm_hash' in creds[user]: if 'ntlm_hash' in creds[user]:
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])
for user in creds:
if -1 != user.find('.'):
new_user = user.replace('.', ',')
creds[new_user] = creds.pop(user)
@staticmethod @staticmethod
def process_trace_telemetry(telemetry_json): def process_trace_telemetry(telemetry_json):
# Nothing to do # Nothing to do
return return
TELEM_PROCESS_DICT = \ TELEM_PROCESS_DICT = \
{ {
'tunnel': Telemetry.process_tunnel_telemetry, 'tunnel': Telemetry.process_tunnel_telemetry,

View File

@ -0,0 +1,88 @@
from datetime import datetime
import dateutil
import flask_restful
from flask import request
import flask_pymongo
from cc.database import mongo
from cc.services.node import NodeService
__author__ = 'itay.mizeretz'
class TelemetryFeed(flask_restful.Resource):
def get(self, **kw):
timestamp = request.args.get('timestamp')
if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code...
telemetries = mongo.db.telemetry.find({})
else:
telemetries = mongo.db.telemetry.find({'timestamp': {'$gt': dateutil.parser.parse(timestamp)}})\
telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)])
return \
{
'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries],
'timestamp': datetime.now().isoformat()
}
@staticmethod
def get_displayed_telemetry(telem):
return \
{
'id': telem['_id'],
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'],
'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem)
}
@staticmethod
def get_tunnel_telem_brief(telem):
tunnel = telem['data']['proxy']
if tunnel is None:
return 'No tunnel is used.'
else:
tunnel_host_ip = tunnel.split(":")[-2].replace("//", "")
tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)['hostname']
return 'Tunnel set up to machine: %s.' % tunnel_host
@staticmethod
def get_state_telem_brief(telem):
if telem['data']['done']:
return 'Monkey died.'
else:
return 'Monkey started.'
@staticmethod
def get_exploit_telem_brief(telem):
target = telem['data']['machine']['ip_addr']
exploiter = telem['data']['exploiter']
result = telem['data']['result']
if result:
return 'Monkey successfully exploited %s using the %s exploiter.' % (target, exploiter)
else:
return 'Monkey failed exploiting %s using the %s exploiter.' % (target, exploiter)
@staticmethod
def get_scan_telem_brief(telem):
return 'Monkey discovered machine %s.' % telem['data']['machine']['ip_addr']
@staticmethod
def get_systeminfo_telem_brief(telem):
return 'Monkey collected system information.'
@staticmethod
def get_trace_telem_brief(telem):
return 'Monkey reached max depth.'
TELEM_PROCESS_DICT = \
{
'tunnel': TelemetryFeed.get_tunnel_telem_brief,
'state': TelemetryFeed.get_state_telem_brief,
'exploit': TelemetryFeed.get_exploit_telem_brief,
'scan': TelemetryFeed.get_scan_telem_brief,
'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief,
'trace': TelemetryFeed.get_trace_telem_brief
}

View File

@ -800,15 +800,23 @@ class ConfigService:
pass pass
@staticmethod @staticmethod
def get_config(): def get_config(is_initial_config=False):
config = mongo.db.config.find_one({'name': 'newconfig'}) or {} config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {}
for field in ('name', '_id'): for field in ('name', '_id'):
config.pop(field, None) config.pop(field, None)
return config return config
@staticmethod @staticmethod
def get_flat_config(): def get_config_value(config_key_as_arr, is_initial_config=False):
config_json = ConfigService.get_config() config_key = reduce(lambda x, y: x+'.'+y, config_key_as_arr)
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
for config_key_part in config_key_as_arr:
config = config[config_key_part]
return config
@staticmethod
def get_flat_config(is_initial_config=False):
config_json = ConfigService.get_config(is_initial_config)
flat_config_json = {} flat_config_json = {}
for i in config_json: for i in config_json:
for j in config_json[i]: for j in config_json[i]:
@ -880,6 +888,16 @@ class ConfigService:
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, ISLAND_PORT) for ip in ips] config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, ISLAND_PORT) for ip in ips]
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], ISLAND_PORT) config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], ISLAND_PORT)
@staticmethod
def save_initial_config_if_needed():
if mongo.db.config.find_one({'name': 'initial'}) is not None:
return
initial_config = mongo.db.config.find_one({'name': 'newconfig'})
initial_config['name'] = 'initial'
initial_config.pop('_id')
mongo.db.config.insert(initial_config)
@staticmethod @staticmethod
def _extend_config_with_default(validator_class): def _extend_config_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"] validate_properties = validator_class.VALIDATORS["properties"]

View File

@ -11,22 +11,22 @@ class EdgeService:
pass pass
@staticmethod @staticmethod
def get_displayed_edge_by_id(edge_id): def get_displayed_edge_by_id(edge_id, for_report=False):
edge = mongo.db.edge.find({"_id": ObjectId(edge_id)})[0] edge = mongo.db.edge.find({"_id": ObjectId(edge_id)})[0]
return EdgeService.edge_to_displayed_edge(edge) return EdgeService.edge_to_displayed_edge(edge, for_report)
@staticmethod @staticmethod
def get_displayed_edges_by_to(to): def get_displayed_edges_by_to(to, for_report=False):
edges = mongo.db.edge.find({"to": ObjectId(to)}) edges = mongo.db.edge.find({"to": ObjectId(to)})
return [EdgeService.edge_to_displayed_edge(edge) for edge in edges] return [EdgeService.edge_to_displayed_edge(edge, for_report) for edge in edges]
@staticmethod @staticmethod
def edge_to_displayed_edge(edge): def edge_to_displayed_edge(edge, for_report=False):
services = [] services = []
os = {} os = {}
if len(edge["scans"]) > 0: if len(edge["scans"]) > 0:
services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"]) services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"], for_report)
os = edge["scans"][-1]["data"]["os"] os = edge["scans"][-1]["data"]["os"]
displayed_edge = EdgeService.edge_to_net_edge(edge) displayed_edge = EdgeService.edge_to_net_edge(edge)
@ -104,8 +104,11 @@ class EdgeService:
return edges return edges
@staticmethod @staticmethod
def services_to_displayed_services(services): def services_to_displayed_services(services, for_report=False):
return [x + ": " + (services[x]['name'] if services[x].has_key('name') else 'unknown') for x in services] if for_report:
return [x for x in services]
else:
return [x + ": " + (services[x]['name'] if 'name' in services[x] else 'unknown') for x in services]
@staticmethod @staticmethod
def edge_to_net_edge(edge): def edge_to_net_edge(edge):

View File

@ -12,11 +12,11 @@ class NodeService:
pass pass
@staticmethod @staticmethod
def get_displayed_node_by_id(node_id): def get_displayed_node_by_id(node_id, for_report=False):
if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id(): if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id():
return NodeService.get_monkey_island_node() return NodeService.get_monkey_island_node()
edges = EdgeService.get_displayed_edges_by_to(node_id) edges = EdgeService.get_displayed_edges_by_to(node_id, for_report)
accessible_from_nodes = [] accessible_from_nodes = []
exploits = [] exploits = []
@ -29,14 +29,14 @@ class NodeService:
return new_node return new_node
# node is infected # node is infected
new_node = NodeService.monkey_to_net_node(monkey) new_node = NodeService.monkey_to_net_node(monkey, for_report)
for key in monkey: for key in monkey:
if key not in ['_id', 'modifytime', 'parent', 'dead', 'description']: if key not in ['_id', 'modifytime', 'parent', 'dead', 'description']:
new_node[key] = monkey[key] new_node[key] = monkey[key]
else: else:
# node is uninfected # node is uninfected
new_node = NodeService.node_to_net_node(node) new_node = NodeService.node_to_net_node(node, for_report)
new_node["ip_addresses"] = node["ip_addresses"] new_node["ip_addresses"] = node["ip_addresses"]
for edge in edges: for edge in edges:
@ -89,6 +89,10 @@ class NodeService:
return True return True
@staticmethod
def get_monkey_label_by_id(monkey_id):
return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
@staticmethod @staticmethod
def get_monkey_label(monkey): def get_monkey_label(monkey):
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0] label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
@ -115,22 +119,24 @@ class NodeService:
return "%s_%s" % (node_type, node_os) return "%s_%s" % (node_type, node_os)
@staticmethod @staticmethod
def monkey_to_net_node(monkey): def monkey_to_net_node(monkey, for_report=False):
label = monkey['hostname'] if for_report else NodeService.get_monkey_label(monkey)
return \ return \
{ {
"id": monkey["_id"], "id": monkey["_id"],
"label": NodeService.get_monkey_label(monkey), "label": label,
"group": NodeService.get_monkey_group(monkey), "group": NodeService.get_monkey_group(monkey),
"os": NodeService.get_monkey_os(monkey), "os": NodeService.get_monkey_os(monkey),
"dead": monkey["dead"], "dead": monkey["dead"],
} }
@staticmethod @staticmethod
def node_to_net_node(node): def node_to_net_node(node, for_report=False):
label = node['os']['version'] if for_report else NodeService.get_node_label(node)
return \ return \
{ {
"id": node["_id"], "id": node["_id"],
"label": NodeService.get_node_label(node), "label": label,
"group": NodeService.get_node_group(node), "group": NodeService.get_node_group(node),
"os": NodeService.get_node_os(node) "os": NodeService.get_node_os(node)
} }
@ -166,6 +172,7 @@ class NodeService:
{ {
"ip_addresses": [ip_address], "ip_addresses": [ip_address],
"exploited": False, "exploited": False,
"creds": [],
"os": "os":
{ {
"type": "unknown", "type": "unknown",
@ -273,3 +280,39 @@ class NodeService:
@staticmethod @staticmethod
def is_any_monkey_exists(): def is_any_monkey_exists():
return mongo.db.monkey.find_one({}) is not None return mongo.db.monkey.find_one({}) is not None
@staticmethod
def is_monkey_finished_running():
return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive()
@staticmethod
def add_credentials_to_monkey(monkey_id, creds):
mongo.db.monkey.update(
{'_id': monkey_id},
{'$push': {'creds': creds}}
)
@staticmethod
def add_credentials_to_node(node_id, creds):
mongo.db.node.update(
{'_id': node_id},
{'$push': {'creds': creds}}
)
@staticmethod
def get_node_or_monkey_by_ip(ip_address):
node = NodeService.get_node_by_ip(ip_address)
if node is not None:
return node
return NodeService.get_monkey_by_ip(ip_address)
@staticmethod
def get_node_or_monkey_by_id(node_id):
node = NodeService.get_node_by_id(node_id)
if node is not None:
return node
return NodeService.get_monkey_by_id(node_id)
@staticmethod
def get_node_hostname(node):
return node['hostname'] if 'hostname' in node else node['os']['version']

View File

@ -0,0 +1,418 @@
import ipaddress
from enum import Enum
from cc.database import mongo
from cc.services.config import ConfigService
from cc.services.edge import EdgeService
from cc.services.node import NodeService
from cc.utils import local_ip_addresses, get_subnets
__author__ = "itay.mizeretz"
class ReportService:
def __init__(self):
pass
EXPLOIT_DISPLAY_DICT = \
{
'SmbExploiter': 'SMB Exploiter',
'WmiExploiter': 'WMI Exploiter',
'SSHExploiter': 'SSH Exploiter',
'RdpExploiter': 'RDP Exploiter',
'SambaCryExploiter': 'SambaCry Exploiter',
'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
'Ms08_067_Exploiter': 'Conficker Exploiter',
'ShellShockExploiter': 'ShellShock Exploiter',
}
class ISSUES_DICT(Enum):
WEAK_PASSWORD = 0
STOLEN_CREDS = 1
ELASTIC = 2
SAMBACRY = 3
SHELLSHOCK = 4
CONFICKER = 5
class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0
TUNNEL = 1
@staticmethod
def get_first_monkey_time():
return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', 1)]).limit(1)[0]['timestamp']
@staticmethod
def get_last_monkey_dead_time():
return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', -1)]).limit(1)[0]['timestamp']
@staticmethod
def get_monkey_duration():
delta = ReportService.get_last_monkey_dead_time() - ReportService.get_first_monkey_time()
st = ""
hours, rem = divmod(delta.seconds, 60 * 60)
minutes, seconds = divmod(rem, 60)
if delta.days > 0:
st += "%d days, " % delta.days
if hours > 0:
st += "%d hours, " % hours
st += "%d minutes and %d seconds" % (minutes, seconds)
return st
@staticmethod
def get_tunnels():
return [
{
'type': 'tunnel',
'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['_id'])),
'dest': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['tunnel']))
}
for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})]
@staticmethod
def get_scanned():
nodes = \
[NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \
+ [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
mongo.db.monkey.find({}, {'_id': 1})]
nodes = [
{
'label': node['label'],
'ip_addresses': node['ip_addresses'],
'accessible_from_nodes':
(x['hostname'] for x in
(NodeService.get_displayed_node_by_id(edge['from'], True)
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))),
'services': node['services']
}
for node in nodes]
return nodes
@staticmethod
def get_exploited():
exploited = \
[NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
mongo.db.monkey.find({}, {'_id': 1})
if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey['_id']))] \
+ [NodeService.get_displayed_node_by_id(node['_id'], True)
for node in mongo.db.node.find({'exploited': True}, {'_id': 1})]
exploited = [
{
'label': monkey['label'],
'ip_addresses': monkey['ip_addresses'],
'exploits': list(set(
[ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if
exploit['result']]))
}
for monkey in exploited]
return exploited
@staticmethod
def get_stolen_creds():
PASS_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'}
creds = []
for telem in mongo.db.telemetry.find(
{'telem_type': 'system_info_collection', 'data.credentials': {'$exists': True}},
{'data.credentials': 1, 'monkey_guid': 1}
):
monkey_creds = telem['data']['credentials']
if len(monkey_creds) == 0:
continue
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
for user in monkey_creds:
for pass_type in monkey_creds[user]:
creds.append(
{
'username': user.replace(',', '.'),
'type': PASS_TYPE_DICT[pass_type],
'origin': origin
}
)
return creds
@staticmethod
def process_general_exploit(exploit):
ip_addr = exploit['data']['machine']['ip_addr']
return {'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)),
'ip_address': ip_addr}
@staticmethod
def process_general_creds_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
for attempt in exploit['data']['attempts']:
if attempt['result']:
processed_exploit['username'] = attempt['user']
if len(attempt['password']) > 0:
processed_exploit['type'] = 'password'
processed_exploit['password'] = attempt['password']
else:
processed_exploit['type'] = 'hash'
return processed_exploit
@staticmethod
def process_smb_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
if processed_exploit['type'] == 'password':
processed_exploit['type'] = 'smb_password'
else:
processed_exploit['type'] = 'smb_pth'
return processed_exploit
@staticmethod
def process_wmi_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
if processed_exploit['type'] == 'password':
processed_exploit['type'] = 'wmi_password'
else:
processed_exploit['type'] = 'wmi_pth'
return processed_exploit
@staticmethod
def process_ssh_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'ssh'
return processed_exploit
@staticmethod
def process_rdp_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'rdp'
return processed_exploit
@staticmethod
def process_sambacry_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'sambacry'
return processed_exploit
@staticmethod
def process_elastic_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'elastic'
return processed_exploit
@staticmethod
def process_conficker_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'conficker'
return processed_exploit
@staticmethod
def process_shellshock_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'shellshock'
urls = exploit['data']['info']['vulnerable_urls']
processed_exploit['port'] = urls[0].split(':')[2].split('/')[0]
processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls]
return processed_exploit
@staticmethod
def process_exploit(exploit):
exploiter_type = exploit['data']['exploiter']
EXPLOIT_PROCESS_FUNCTION_DICT = {
'SmbExploiter': ReportService.process_smb_exploit,
'WmiExploiter': ReportService.process_wmi_exploit,
'SSHExploiter': ReportService.process_ssh_exploit,
'RdpExploiter': ReportService.process_rdp_exploit,
'SambaCryExploiter': ReportService.process_sambacry_exploit,
'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
'Ms08_067_Exploiter': ReportService.process_conficker_exploit,
'ShellShockExploiter': ReportService.process_shellshock_exploit,
}
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
@staticmethod
def get_exploits():
exploits = []
for exploit in mongo.db.telemetry.find({'telem_type': 'exploit', 'data.result': True}):
new_exploit = ReportService.process_exploit(exploit)
if new_exploit not in exploits:
exploits.append(new_exploit)
return exploits
@staticmethod
def get_monkey_subnets(monkey_guid):
network_info = mongo.db.telemetry.find_one(
{'telem_type': 'system_info_collection', 'monkey_guid': monkey_guid},
{'data.network_info.networks': 1}
)
if network_info is None:
return []
return \
[
ipaddress.ip_interface(unicode(network['addr'] + '/' + network['netmask'])).network
for network in network_info['data']['network_info']['networks']
]
@staticmethod
def get_cross_segment_issues():
issues = []
island_ips = local_ip_addresses()
for monkey in mongo.db.monkey.find({'tunnel': {'$exists': False}}, {'tunnel': 1, 'guid': 1, 'hostname': 1}):
found_good_ip = False
monkey_subnets = ReportService.get_monkey_subnets(monkey['guid'])
for subnet in monkey_subnets:
for ip in island_ips:
if ipaddress.ip_address(unicode(ip)) in subnet:
found_good_ip = True
break
if found_good_ip:
break
if not found_good_ip:
issues.append(
{'type': 'cross_segment', 'machine': monkey['hostname'],
'networks': [str(subnet) for subnet in monkey_subnets],
'server_networks': [str(subnet) for subnet in get_subnets()]}
)
return issues
@staticmethod
def get_issues():
issues = ReportService.get_exploits() + ReportService.get_tunnels() + ReportService.get_cross_segment_issues()
issues_dict = {}
for issue in issues:
machine = issue['machine']
if machine not in issues_dict:
issues_dict[machine] = []
issues_dict[machine].append(issue)
return issues_dict
@staticmethod
def get_manual_monkeys():
return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if
NodeService.get_monkey_manual_run(monkey)]
@staticmethod
def get_config_users():
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_user_list'], True)
@staticmethod
def get_config_passwords():
return ConfigService.get_config_value(['basic', 'credentials', 'exploit_password_list'], True)
@staticmethod
def get_config_exploits():
exploits_config_value = ['exploits', 'general', 'exploiter_classes']
default_exploits = ConfigService.get_default_config()
for namespace in exploits_config_value:
default_exploits = default_exploits[namespace]
exploits = ConfigService.get_config_value(exploits_config_value, True)
if exploits == default_exploits:
return ['default']
return [ReportService.EXPLOIT_DISPLAY_DICT[exploit] for exploit in
exploits]
@staticmethod
def get_config_ips():
if ConfigService.get_config_value(['basic_network', 'network_range', 'range_class'], True) != 'FixedRange':
return []
return ConfigService.get_config_value(['basic_network', 'network_range', 'range_fixed'], True)
@staticmethod
def get_config_scan():
return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True)
@staticmethod
def get_issues_overview(issues, config_users, config_passwords):
issues_byte_array = [False] * 6
for machine in issues:
for issue in issues[machine]:
if issue['type'] == 'elastic':
issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True
elif issue['type'] == 'sambacry':
issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True
elif issue['type'] == 'shellshock':
issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True
elif issue['type'] == 'conficker':
issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
issue['username'] in config_users:
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
return issues_byte_array
@staticmethod
def get_warnings_overview(issues):
warnings_byte_array = [False] * 2
for machine in issues:
for issue in issues[machine]:
if issue['type'] == 'cross_segment':
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
elif issue['type'] == 'tunnel':
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
return warnings_byte_array
@staticmethod
def is_report_generated():
generated_report = mongo.db.report.find_one({'name': 'generated_report'})
if generated_report is None:
return False
return generated_report['value']
@staticmethod
def set_report_generated():
mongo.db.report.update(
{'name': 'generated_report'},
{'$set': {'value': True}},
upsert=True)
@staticmethod
def get_report():
issues = ReportService.get_issues()
config_users = ReportService.get_config_users()
config_passwords = ReportService.get_config_passwords()
report = \
{
'overview':
{
'manual_monkeys': ReportService.get_manual_monkeys(),
'config_users': config_users,
'config_passwords': config_passwords,
'config_exploits': ReportService.get_config_exploits(),
'config_ips': ReportService.get_config_ips(),
'config_scan': ReportService.get_config_scan(),
'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"),
'monkey_duration': ReportService.get_monkey_duration(),
'issues': ReportService.get_issues_overview(issues, config_users, config_passwords),
'warnings': ReportService.get_warnings_overview(issues)
},
'glance':
{
'scanned': ReportService.get_scanned(),
'exploited': ReportService.get_exploited(),
'stolen_creds': ReportService.get_stolen_creds()
},
'recommendations':
{
'issues': issues
}
}
finished_run = NodeService.is_monkey_finished_running()
if finished_run:
ReportService.set_report_generated()
return report
@staticmethod
def did_exploit_type_succeed(exploit_type):
return mongo.db.edge.count(
{'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}},
limit=1) > 0

View File

@ -67,6 +67,7 @@
"js-file-download": "^0.4.1", "js-file-download": "^0.4.1",
"normalize.css": "^4.0.0", "normalize.css": "^4.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"rc-progress": "^2.2.5",
"react": "^15.6.1", "react": "^15.6.1",
"react-bootstrap": "^0.31.2", "react-bootstrap": "^0.31.2",
"react-copy-to-clipboard": "^5.0.0", "react-copy-to-clipboard": "^5.0.0",
@ -80,6 +81,7 @@
"react-modal-dialog": "^4.0.7", "react-modal-dialog": "^4.0.7",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-table": "^6.7.4",
"react-toggle": "^4.0.1", "react-toggle": "^4.0.1",
"redux": "^3.7.2" "redux": "^3.7.2"
} }

View File

@ -16,8 +16,10 @@ require('normalize.css/normalize.css');
require('react-data-components/css/table-twbs.css'); require('react-data-components/css/table-twbs.css');
require('styles/App.css'); require('styles/App.css');
require('react-toggle/style.css'); require('react-toggle/style.css');
require('react-table/react-table.css');
let logoImage = require('../images/monkey-logo.png'); let logoImage = require('../images/monkey-icon.svg');
let infectionMonkeyImage = require('../images/infection-monkey.svg');
let guardicoreLogoImage = require('../images/guardicore-logo.png'); let guardicoreLogoImage = require('../images/guardicore-logo.png');
class AppComponent extends React.Component { class AppComponent extends React.Component {
@ -27,7 +29,8 @@ class AppComponent extends React.Component {
completedSteps: { completedSteps: {
run_server: true, run_server: true,
run_monkey: false, run_monkey: false,
infection_done: false infection_done: false,
report_done: false
} }
}; };
} }
@ -66,7 +69,8 @@ class AppComponent extends React.Component {
<Row> <Row>
<Col sm={3} md={2} className="sidebar"> <Col sm={3} md={2} className="sidebar">
<div className="header"> <div className="header">
<img src={logoImage} alt="Infection Monkey"/> <img src={logoImage} style={{width: '5vw', margin: '15px'}}/>
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt="Infection Monkey"/>
</div> </div>
<ul className="navigation"> <ul className="navigation">
@ -101,6 +105,9 @@ class AppComponent extends React.Component {
<NavLink to="/report"> <NavLink to="/report">
<span className="number">4.</span> <span className="number">4.</span>
Security Report Security Report
{ this.state.completedSteps.report_done ?
<Icon name="check" className="pull-right checkmark text-success"/>
: ''}
</NavLink> </NavLink>
</li> </li>
<li> <li>

View File

@ -0,0 +1,52 @@
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let getGroupsOptions = () => {
let groupOptions = {};
for (let groupName of groupNames) {
groupOptions[groupName] =
{
shape: 'image',
size: 50,
image: require('../../images/nodes/' + groupName + '.png')
};
}
return groupOptions;
};
export const options = {
autoResize: true,
layout: {
improvedLayout: false
},
edges: {
width: 2,
smooth: {
type: 'curvedCW'
}
},
physics: {
barnesHut: {
gravitationalConstant: -120000,
avoidOverlap: 0.5
},
minVelocity: 0.75
},
groups: getGroupsOptions()
};
export function edgeGroupToColor(group) {
switch (group) {
case 'exploited':
return '#c00';
case 'tunnel':
return '#0058aa';
case 'scan':
return '#f90';
case 'island':
return '#aaa';
}
return 'black';
}

View File

@ -2,48 +2,10 @@ import React from 'react';
import {Col} from 'react-bootstrap'; import {Col} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import PreviewPane from 'components/preview-pane/PreviewPane'; import PreviewPane from 'components/map/preview-pane/PreviewPane';
import {ReactiveGraph} from '../reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {ModalContainer, ModalDialog} from 'react-modal-dialog'; import {ModalContainer, ModalDialog} from 'react-modal-dialog';
import {options, edgeGroupToColor} from 'components/map/MapOptions';
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let getGroupsOptions = () => {
let groupOptions = {};
for (let groupName of groupNames) {
groupOptions[groupName] =
{
shape: 'image',
size: 50,
image: require('../../images/nodes/' + groupName + '.png')
};
}
return groupOptions;
};
let options = {
autoResize: true,
layout: {
improvedLayout: false
},
edges: {
width: 2,
smooth: {
type: 'curvedCW'
}
},
physics: {
barnesHut: {
gravitationalConstant: -120000,
avoidOverlap: 0.5
},
minVelocity: 0.75
},
groups: getGroupsOptions()
};
class MapPageComponent extends React.Component { class MapPageComponent extends React.Component {
constructor(props) { constructor(props) {
@ -53,7 +15,9 @@ class MapPageComponent extends React.Component {
selected: null, selected: null,
selectedType: null, selectedType: null,
killPressed: false, killPressed: false,
showKillDialog: false showKillDialog: false,
telemetry: [],
telemetryLastTimestamp: null
}; };
} }
@ -61,41 +25,47 @@ class MapPageComponent extends React.Component {
select: event => this.selectionChanged(event) select: event => this.selectionChanged(event)
}; };
static edgeGroupToColor(group) {
switch (group) {
case 'exploited':
return '#c00';
case 'tunnel':
return '#0058aa';
case 'scan':
return '#f90';
case 'island':
return '#aaa';
}
return 'black';
}
componentDidMount() { componentDidMount() {
this.updateMapFromServer(); this.updateMapFromServer();
this.interval = setInterval(this.updateMapFromServer, 1000); this.interval = setInterval(this.timedEvents, 1000);
} }
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.interval); clearInterval(this.interval);
} }
timedEvents = () => {
this.updateMapFromServer();
this.updateTelemetryFromServer();
};
updateMapFromServer = () => { updateMapFromServer = () => {
fetch('/api/netmap') fetch('/api/netmap')
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
res.edges.forEach(edge => { res.edges.forEach(edge => {
edge.color = MapPageComponent.edgeGroupToColor(edge.group); edge.color = edgeGroupToColor(edge.group);
}); });
this.setState({graph: res}); this.setState({graph: res});
this.props.onStatusChange(); this.props.onStatusChange();
}); });
}; };
updateTelemetryFromServer = () => {
fetch('/api/telemetry-feed?timestamp='+this.state.telemetryLastTimestamp)
.then(res => res.json())
.then(res => {
let newTelem = this.state.telemetry.concat(res['telemetries']);
this.setState(
{
telemetry: newTelem,
telemetryLastTimestamp: res['timestamp']
});
this.props.onStatusChange();
});
};
selectionChanged(event) { selectionChanged(event) {
if (event.nodes.length === 1) { if (event.nodes.length === 1) {
fetch('/api/netmap/node?id=' + event.nodes[0]) fetch('/api/netmap/node?id=' + event.nodes[0])
@ -156,6 +126,26 @@ class MapPageComponent extends React.Component {
) )
}; };
renderTelemetryEntry(telemetry) {
return (
<div key={telemetry.id}>
<span className="date">{telemetry.timestamp}</span>
<span className="source"> {telemetry.hostname}:</span>
<span className="event"> {telemetry.brief}</span>
</div>
);
}
renderTelemetryConsole() {
return (
<div className="telemetry-console">
{
this.state.telemetry.map(this.renderTelemetryEntry)
}
</div>
);
}
render() { render() {
return ( return (
<div> <div>
@ -174,17 +164,7 @@ class MapPageComponent extends React.Component {
<b style={{color: '#aeaeae'}}> | </b> <b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <i className="fa fa-lg fa-minus" style={{color: '#a9aaa9'}} /></span> <span>Island Communication <i className="fa fa-lg fa-minus" style={{color: '#a9aaa9'}} /></span>
</div> </div>
{ { this.renderTelemetryConsole() }
/*
<div className="telemetry-console">
<div>
<span className="date">2017-10-16 16:00:05</span>
<span className="source"> monkey-elastic</span>
<span className="event"> bla bla</span>
</div>
</div>
*/
}
<div style={{height: '80vh'}}> <div style={{height: '80vh'}}>
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/> <ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
</div> </div>

View File

@ -1,23 +1,692 @@
import React from 'react'; import React from 'react';
import {Col} from 'react-bootstrap'; import {Button, Col} from 'react-bootstrap';
import BreachedServers from 'components/report-components/BreachedServers';
import ScannedServers from 'components/report-components/ScannedServers';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, options} from 'components/map/MapOptions';
import StolenPasswords from 'components/report-components/StolenPasswords';
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
import {Line} from 'rc-progress';
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
let monkeyLogoImage = require('../../images/monkey-icon.svg');
class ReportPageComponent extends React.Component { class ReportPageComponent extends React.Component {
Issue =
{
WEAK_PASSWORD: 0,
STOLEN_CREDS: 1,
ELASTIC: 2,
SAMBACRY: 3,
SHELLSHOCK: 4,
CONFICKER: 5
};
Warning =
{
CROSS_SEGMENT: 0,
TUNNEL: 1
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
report: {},
graph: {nodes: [], edges: []},
allMonkeysAreDead: false,
runStarted: true
};
}
componentDidMount() {
this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
this.updateMapFromServer();
this.interval = setInterval(this.updateMapFromServer, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
} }
render() { render() {
let content;
if (Object.keys(this.state.report).length === 0) {
if (this.state.runStarted) {
content = (<h1>Generating Report...</h1>);
} else {
content =
<p className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
You have to run a monkey before generating a report!
</p>;
}
} else {
content = this.generateReportContent();
}
return ( return (
<Col xs={12} lg={8}> <Col xs={12} lg={8}>
<h1 className="page-title">4. Security Report</h1> <h1 className="page-title no-print">4. Security Report</h1>
<div style={{'fontSize': '1.2em'}}> <div style={{'fontSize': '1.2em'}}>
<p> {content}
Under construction
</p>
</div> </div>
</Col> </Col>
); );
} }
updateMonkeysRunning = () => {
return fetch('/api')
.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']),
runStarted: res['completed_steps']['run_monkey']
});
return res;
});
};
updateMapFromServer = () => {
fetch('/api/netmap')
.then(res => res.json())
.then(res => {
res.edges.forEach(edge => {
edge.color = edgeGroupToColor(edge.group);
});
this.setState({graph: res});
this.props.onStatusChange();
});
};
getReportFromServer(res) {
if (res['completed_steps']['run_monkey']) {
fetch('/api/report')
.then(res => res.json())
.then(res => {
this.setState({
report: res
});
});
}
}
generateReportContent() {
return (
<div>
<div className="text-center no-print" style={{marginBottom: '20px'}}>
<Button bsSize="large" onClick={() => {
print();
}}><i className="glyphicon glyphicon-print"/> Print Report</Button>
</div>
<div className="report-page">
{this.generateReportHeader()}
<hr/>
{this.generateReportOverviewSection()}
{this.generateReportFindingsSection()}
{this.generateReportRecommendationsSection()}
{this.generateReportGlanceSection()}
{this.generateReportFooter()}
</div>
<div className="text-center no-print" style={{marginTop: '20px'}}>
<Button bsSize="large" onClick={() => {
print();
}}><i className="glyphicon glyphicon-print"/> Print Report</Button>
</div>
</div>
);
}
generateReportHeader() {
return (
<div id="header" className="row justify-content-between">
<Col xs={8}>
<div>
<h1 style={{marginTop: '0px', marginBottom: '5px', color: '#666666', fontFamily: 'Alegreya'}}>Security Report</h1>
<h1 style={{marginTop: '0px', marginBottom: '0px', color: '#ffcc00', fontFamily: 'Alegreya'}}>Infection <b>Monkey</b></h1>
</div>
</Col>
<Col xs={4}>
<img src={monkeyLogoImage}
style={{
float: 'right',
width: '80px'
}}/>
</Col>
</div>
);
}
generateReportOverviewSection() {
return (
<div id="overview">
<h2>
Overview
</h2>
{
this.state.report.glance.exploited.length > 0 ?
(<p className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
Critical security issues were detected!
</p>) :
(<p className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
No critical security issues were detected.
</p>)
}
{
this.state.allMonkeysAreDead ?
''
:
(<p className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
Some monkeys are still running. To get the best report it's best to wait for all of them to finish
running.
</p>)
}
{
this.state.report.glance.exploited.length > 0 ?
''
:
<p className="alert alert-info">
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
To improve the monkey's detection rates, try adding users and passwords and enable the "Local
network
scan" config value under <b>Basic - Network</b>.
</p>
}
<p>
The first monkey run was started on <span
className="label label-info">{this.state.report.overview.monkey_start_time}</span>. After <span
className="label label-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
propagation attempts.
</p>
<p>
The monkey started propagating from the following machines where it was manually installed:
<ul>
{this.state.report.overview.manual_monkeys.map(x => <li>{x}</li>)}
</ul>
</p>
<p>
The monkeys were run with the following configuration:
</p>
{
this.state.report.overview.config_users.length > 0 ?
<p>
Usernames used for brute-forcing:
<ul>
{this.state.report.overview.config_users.map(x => <li>{x}</li>)}
</ul>
Passwords used for brute-forcing:
<ul>
{this.state.report.overview.config_passwords.map(x => <li>{x.substr(0, 3) + '******'}</li>)}
</ul>
</p>
:
<p>
Brute forcing uses stolen credentials only. No credentials were supplied during Monkeys
configuration.
</p>
}
{
this.state.report.overview.config_exploits.length > 0 ?
(
this.state.report.overview.config_exploits[0] === 'default' ?
''
:
<p>
The Monkey uses the following exploit methods:
<ul>
{this.state.report.overview.config_exploits.map(x => <li>{x}</li>)}
</ul>
</p>
)
:
<p>
No exploits are used by the Monkey.
</p>
}
{
this.state.report.overview.config_ips.length > 0 ?
<p>
The Monkey scans the following IPs:
<ul>
{this.state.report.overview.config_ips.map(x => <li>{x}</li>)}
</ul>
</p>
:
''
}
{
this.state.report.overview.config_scan ?
''
:
<p>
Note: Monkeys were configured to avoid scanning of the local network.
</p>
}
</div>
);
}
generateReportFindingsSection() {
return (
<div id="findings">
<h3>
Security Findings
</h3>
<div>
<h3>
Immediate Threats
</h3>
{
this.state.report.overview.issues.filter(function (x) {
return x === true;
}).length > 0 ?
<div>
During this simulated attack the Monkey uncovered <span
className="label label-warning">
{this.state.report.overview.issues.filter(function (x) {
return x === true;
}).length} threats</span>:
<ul>
{this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
<li>Stolen credentials are used to exploit other machines.</li> : null}
{this.state.report.overview.issues[this.Issue.ELASTIC] ?
<li>Elasticsearch servers are vulnerable to <a
href="https://www.cvedetails.com/cve/cve-2015-1427">CVE-2015-1427</a>.
</li> : null}
{this.state.report.overview.issues[this.Issue.SAMBACRY] ?
<li>Samba servers are vulnerable to SambaCry (<a
href="https://www.samba.org/samba/security/CVE-2017-7494.html"
>CVE-2017-7494</a>).</li> : null}
{this.state.report.overview.issues[this.Issue.SHELLSHOCK] ?
<li>Machines are vulnerable to Shellshock (<a
href="https://www.cvedetails.com/cve/CVE-2014-6271">CVE-2014-6271</a>).
</li> : null}
{this.state.report.overview.issues[this.Issue.CONFICKER] ?
<li>Machines are vulnerable to Conficker (<a
href="https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/2008/ms08-067"
>MS08-067</a>).</li> : null}
{this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] ?
<li>Machines are accessible using passwords supplied by the user during the Monkeys
configuration.</li> : null}
</ul>
</div>
:
<div>
During this simulated attack the Monkey uncovered <span
className="label label-success">0 threats</span>.
</div>
}
</div>
<div>
<h3>
Potential Security Issues
</h3>
{
this.state.report.overview.warnings.filter(function (x) {
return x === true;
}).length > 0 ?
<div>
The Monkey uncovered the following possible set of issues:
<ul>
{this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ?
<li>Weak segmentation - Machines from different segments are able to
communicate.</li> : null}
{this.state.report.overview.warnings[this.Warning.TUNNEL] ?
<li>Weak segmentation - machines were able to communicate over unused ports.</li> : null}
</ul>
</div>
:
<div>
The Monkey did not find any issues.
</div>
}
</div>
</div>
);
}
generateReportRecommendationsSection() {
return (
<div id="recommendations">
<h3>
Recommendations
</h3>
<div>
{this.generateIssues(this.state.report.recommendations.issues)}
</div>
</div>
);
}
generateReportGlanceSection() {
let exploitPercentage =
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
return (
<div id="glance">
<h3>
The Network from the Monkey's Eyes
</h3>
<div>
<p>
The Monkey discovered <span
className="label label-warning">{this.state.report.glance.scanned.length}</span> machines and
successfully breached <span
className="label label-danger">{this.state.report.glance.exploited.length}</span> of them.
</p>
<div className="text-center" style={{margin: '10px'}}>
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth="4"
trailWidth="4"
strokeColor="#d9534f" trailColor="#f0ad4e"/>
<b>{Math.round(exploitPercentage)}% of scanned machines exploited</b>
</div>
</div>
<p>
From the attacker's point of view, the network looks like this:
</p>
<div className="map-legend">
<b>Legend: </b>
<span>Exploit <i className="fa fa-lg fa-minus" style={{color: '#cc0200'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Scan <i className="fa fa-lg fa-minus" style={{color: '#ff9900'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Tunnel <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <i className="fa fa-lg fa-minus" style={{color: '#a9aaa9'}}/></span>
</div>
<div style={{position: 'relative', height: '80vh'}}>
<ReactiveGraph graph={this.state.graph} options={options}/>
</div>
<div style={{marginBottom: '20px'}}>
<BreachedServers data={this.state.report.glance.exploited}/>
</div>
<div style={{marginBottom: '20px'}}>
<ScannedServers data={this.state.report.glance.scanned}/>
</div>
<div>
<StolenPasswords data={this.state.report.glance.stolen_creds}/>
</div>
</div>
);
}
generateReportFooter() {
return (
<div id="footer" className="text-center" style={{marginTop: '20px'}}>
For questions, suggestions or any other feedback
contact: <a href="mailto://labs@guardicore.com" className="no-print">labs@guardicore.com</a>
<div className="force-print" style={{display: 'none'}}>labs@guardicore.com</div>
<img src={guardicoreLogoImage} alt="GuardiCore" className="center-block" style={{height: '50px'}}/>
</div>
);
}
generateInfoBadges(data_array) {
return data_array.map(badge_data => <span className="label label-info" style={{margin: '2px'}}>{badge_data}</span>);
}
generateShellshockPathListBadges(paths) {
return paths.map(path => <span className="label label-warning" style={{margin: '2px'}}>{path}</span>);
}
generateSmbPasswordIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SMB</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="label label-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
}
generateSmbPthIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SMB</span> attack.
<br/>
The Monkey used a pass-the-hash attack over SMB protocol with user <span
className="label label-success">{issue.username}</span>.
</CollapsibleWellComponent>
</li>
);
}
generateWmiPasswordIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">WMI</span> attack.
<br/>
The Monkey authenticated over the WMI protocol with user <span
className="label label-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
}
generateWmiPthIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">WMI</span> attack.
<br/>
The Monkey used a pass-the-hash attack over WMI protocol with user <span
className="label label-success">{issue.username}</span>.
</CollapsibleWellComponent>
</li>
);
}
generateSshIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SSH</span> attack.
<br/>
The Monkey authenticated over the SSH protocol with user <span
className="label label-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
}
generateRdpIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">RDP</span> attack.
<br/>
The Monkey authenticated over the RDP protocol with user <span
className="label label-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
}
generateSambaCryIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<br/>
Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">SambaCry</span> attack.
<br/>
The Monkey authenticated over the SMB protocol with user <span
className="label label-success">{issue.username}</span> and its password, and used the SambaCry
vulnerability.
</CollapsibleWellComponent>
</li>
);
}
generateElasticIssue(issue) {
return (
<li>
Update your Elastic Search server to version 1.4.3 and up.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
className="label label-danger">Elastic Groovy</span> attack.
<br/>
The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
</CollapsibleWellComponent>
</li>
);
}
generateShellshockIssue(issue) {
return (
<li>
Update your Bash to a ShellShock-patched version.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">ShellShock</span> attack.
<br/>
The attack was made possible because the HTTP server running on TCP port <span
className="label label-info">{issue.port}</span> was vulnerable to a shell injection attack on the
paths: {this.generateShellshockPathListBadges(issue.paths)}.
</CollapsibleWellComponent>
</li>
);
}
generateConfickerIssue(issue) {
return (
<li>
Install the latest Windows updates or upgrade to a newer operating system.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">Conficker</span> attack.
<br/>
The attack was made possible because the target machine used an outdated and unpatched operating system
vulnerable to Conficker.
</CollapsibleWellComponent>
</li>
);
}
generateCrossSegmentIssue(issue) {
return (
<li>
Segment your network and make sure there is no communication between machines from different segments.
<CollapsibleWellComponent>
The network can probably be segmented. A monkey instance on <span
className="label label-primary">{issue.machine}</span> in the
networks {this.generateInfoBadges(issue.networks)}
could directly access the Monkey Island C&C server in the
networks {this.generateInfoBadges(issue.server_networks)}.
</CollapsibleWellComponent>
</li>
);
}
generateTunnelIssue(issue) {
return (
<li>
Use micro-segmentation policies to disable communication other than the required.
<CollapsibleWellComponent>
Machines are not locked down at port level. Network tunnel was set up from <span
className="label label-primary">{issue.machine}</span> to <span
className="label label-primary">{issue.dest}</span>.
</CollapsibleWellComponent>
</li>
);
}
generateIssue = (issue) => {
let data;
switch (issue.type) {
case 'smb_password':
data = this.generateSmbPasswordIssue(issue);
break;
case 'smb_pth':
data = this.generateSmbPthIssue(issue);
break;
case 'wmi_password':
data = this.generateWmiPasswordIssue(issue);
break;
case 'wmi_pth':
data = this.generateWmiPthIssue(issue);
break;
case 'ssh':
data = this.generateSshIssue(issue);
break;
case 'rdp':
data = this.generateRdpIssue(issue);
break;
case 'sambacry':
data = this.generateSambaCryIssue(issue);
break;
case 'elastic':
data = this.generateElasticIssue(issue);
break;
case 'shellshock':
data = this.generateShellshockIssue(issue);
break;
case 'conficker':
data = this.generateConfickerIssue(issue);
break;
case 'cross_segment':
data = this.generateCrossSegmentIssue(issue);
break;
case 'tunnel':
data = this.generateTunnelIssue(issue);
break;
}
return data;
};
generateIssues = (issues) => {
let issuesDivArray = [];
for (let machine of Object.keys(issues)) {
issuesDivArray.push(
<li>
<h4><b>{machine}</b></h4>
<ol>
{issues[machine].map(this.generateIssue)}
</ol>
</li>
);
}
return <ul>{issuesDivArray}</ul>;
};
} }
export default ReportPageComponent; export default ReportPageComponent;

View File

@ -0,0 +1,45 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
if (val.length === 0) {
return '';
}
return val.reduce((total, new_str) => total + ', ' + new_str);
};
const columns = [
{
Header: 'Breached Servers',
columns: [
{Header: 'Machine', accessor: 'label'},
{Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
{Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
]
}
];
const pageSize = 10;
class BreachedServersComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default BreachedServersComponent;

View File

@ -0,0 +1,40 @@
import React from 'react';
import {Button, Collapse, Well} from 'react-bootstrap';
class CollapsibleWellComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
render() {
let well =
(
<Well style={{margin: '10px'}}>
{this.props.children}
</Well>
);
return (
<div>
<div className="no-print">
<Button onClick={() => this.setState({open: !this.state.open})} bsStyle="link">
Read More...
</Button>
<Collapse in={this.state.open}>
<div>
{well}
</div>
</Collapse>
</div>
<div className="force-print" style={{display: 'none'}}>
{well}
</div>
</div>
);
}
}
export default CollapsibleWellComponent;

View File

@ -0,0 +1,46 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
if (val.length === 0) {
return '';
}
return val.reduce((total, new_str) => total + ', ' + new_str);
};
const columns = [
{
Header: 'Scanned Servers',
columns: [
{ Header: 'Machine', accessor: 'label'},
{ Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
{ Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)},
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
]
}
];
const pageSize = 10;
class ScannedServersComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default ScannedServersComponent;

View File

@ -0,0 +1,38 @@
import React from 'react';
import ReactTable from 'react-table'
const columns = [
{
Header: 'Stolen Credentials',
columns: [
{ Header: 'Username', accessor: 'username'},
{ Header: 'Type', accessor: 'type'},
{ Header: 'Stolen From', accessor: 'origin'}
]
}
];
const pageSize = 10;
class StolenPasswordsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default StolenPasswordsComponent;

View File

@ -0,0 +1,32 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97.577 11.46">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<title>14cbedff-3eed-4f8f-abb7-fffe92867ded</title>
<g>
<path class="cls-1" d="M89.22,6.874l1.819-.159a3.153,3.153,0,1,1,.124-1.065,2.306,2.306,0,0,1-.005.492l-4.489.393a1.257,1.257,0,0,0,1.414.989,1.417,1.417,0,0,0,1.06-.56A.6.6,0,0,1,89.22,6.874Zm.087-1.767a1.311,1.311,0,0,0-1.443-.916,1.282,1.282,0,0,0-1.249,1.151Z"/>
<path class="cls-1" d="M67.715,4.191a1.622,1.622,0,1,1-1.622,1.622,1.624,1.624,0,0,1,1.622-1.622m0-1.542a3.164,3.164,0,1,0,3.164,3.164A3.164,3.164,0,0,0,67.715,2.65Z"/>
<path class="cls-1" d="M54.893,8.808,55.53.648A6.958,6.958,0,0,1,56.736.564a6.743,6.743,0,0,1,1.193.084L58.7,3.7q.324,1.332.456,2.148h.071q.132-.816.456-2.148L60.45.648a6.914,6.914,0,0,1,1.2-.084,6.7,6.7,0,0,1,1.188.084l.636,8.16a3.218,3.218,0,0,1-.863.1,5.125,5.125,0,0,1-.864-.06l-.192-3.48q-.1-1.656-.1-2.724h-.084L60.126,7.62a5.447,5.447,0,0,1-.93.06,5.9,5.9,0,0,1-.954-.06L57.006,2.64h-.084q-.023,1.464-.1,2.724l-.192,3.48a5.125,5.125,0,0,1-.864.06A3.366,3.366,0,0,1,54.893,8.808Z"/>
<path class="cls-1" d="M77.094,4.86v2.5a2.089,2.089,0,0,0,.288,1.188,1.611,1.611,0,0,1-1.05.384.983.983,0,0,1-.8-.276,1.321,1.321,0,0,1-.223-.84V5.2a1.33,1.33,0,0,0-.12-.648.446.446,0,0,0-.42-.2,1.386,1.386,0,0,0-.983.468v4.02A4.716,4.716,0,0,1,72.9,8.9a5.113,5.113,0,0,1-.906-.072V2.856l.084-.084h.672a.9.9,0,0,1,.948.72,2.669,2.669,0,0,1,1.7-.744,1.477,1.477,0,0,1,1.26.576A2.5,2.5,0,0,1,77.094,4.86Z"/>
<path class="cls-1" d="M81.3,6.408l-.4.012V8.832a4.606,4.606,0,0,1-.864.072,5,5,0,0,1-.888-.072V.084L79.241,0h.7a.973.973,0,0,1,.75.246,1.243,1.243,0,0,1,.222.834V4.944h.216a.369.369,0,0,0,.336-.216l.685-1.26a1.062,1.062,0,0,1,1.008-.66q.347,0,1,.024l.071.1-.972,1.824a1.885,1.885,0,0,1-.6.672,1.7,1.7,0,0,1,1.02,1.044l.3.888a4.932,4.932,0,0,0,.307.708.941.941,0,0,0,.27.3,1.512,1.512,0,0,1-1.2.564.839.839,0,0,1-.582-.18,1.534,1.534,0,0,1-.354-.636l-.36-1.056a1.225,1.225,0,0,0-.306-.516A.675.675,0,0,0,81.3,6.408Z"/>
<path class="cls-1" d="M97.577,3.036l-1.5,5.628a5.042,5.042,0,0,1-1,2.1,2.437,2.437,0,0,1-1.9.7,4.322,4.322,0,0,1-1.416-.24V11a2.268,2.268,0,0,1,.09-.51,1.213,1.213,0,0,1,.27-.54,3.946,3.946,0,0,0,1.057.18,1.231,1.231,0,0,0,1.212-1.044l.048-.168q-1.008-.024-1.2-.744L91.805,2.988a2.4,2.4,0,0,1,.989-.24,1.049,1.049,0,0,1,.666.168,1.108,1.108,0,0,1,.313.6l.6,2.376q.1.348.288,1.6a.094.094,0,0,0,.108.084l1.08-4.716a3.485,3.485,0,0,1,.786-.072,2.786,2.786,0,0,1,.882.132Z"/>
</g>
<g>
<path class="cls-1" d="M0,8.937V.741A1.882,1.882,0,0,1,.414.705,2.229,2.229,0,0,1,.852.741v8.2a2.231,2.231,0,0,1-.432.036A2.056,2.056,0,0,1,0,8.937Z"/>
<path class="cls-1" d="M8.232,4.809V7.833a2.871,2.871,0,0,0,.12,1,.7.7,0,0,1-.517.18q-.42,0-.42-.54V5.085a2.2,2.2,0,0,0-.191-1.074.747.747,0,0,0-.7-.318,2.332,2.332,0,0,0-1.044.27,2.7,2.7,0,0,0-.888.69V8.937a1.994,1.994,0,0,1-.408.036,1.987,1.987,0,0,1-.408-.036V3.093l.072-.072h.312a.384.384,0,0,1,.348.132.831.831,0,0,1,.085.432v.24a2.983,2.983,0,0,1,.966-.636A2.908,2.908,0,0,1,6.7,2.949a1.335,1.335,0,0,1,1.17.516A2.272,2.272,0,0,1,8.232,4.809Z"/>
<path class="cls-1" d="M11.147,8.937V3.669l-1.031.012a1.473,1.473,0,0,1-.036-.312,1.639,1.639,0,0,1,.036-.324l1,.024q-.085-1.115-.084-1.272A1.769,1.769,0,0,1,11.472.555,1.538,1.538,0,0,1,12.666.069a3.728,3.728,0,0,1,1.386.252.818.818,0,0,1-.2.564A3.881,3.881,0,0,0,12.756.693a.883.883,0,0,0-.708.294,1.226,1.226,0,0,0-.252.822,9.977,9.977,0,0,0,.1,1.26l1.776-.024a1.716,1.716,0,0,1,.036.324,1.54,1.54,0,0,1-.036.312l-1.716-.012V8.937a2.258,2.258,0,0,1-.8,0Z"/>
<path class="cls-1" d="M32.352,3.045a1.716,1.716,0,0,1,.036.324,1.54,1.54,0,0,1-.036.312l-1.524-.012v4.14q0,.492.468.492h.925a1,1,0,0,1,.071.372,1.405,1.405,0,0,1-.012.24,8.879,8.879,0,0,1-1.122.072,1.268,1.268,0,0,1-.846-.246.87.87,0,0,1-.288-.7V3.669l-.828.012a1.336,1.336,0,0,1-.036-.306,1.664,1.664,0,0,1,.036-.33l.828.012v-1.3a.794.794,0,0,1,.09-.432.4.4,0,0,1,.354-.132h.288l.072.072v1.8Z"/>
<path class="cls-1" d="M35.472,3.837v5.1a2.324,2.324,0,0,1-.816,0V4.257a.7.7,0,0,0-.108-.456.5.5,0,0,0-.384-.12h-.107a1.288,1.288,0,0,1-.036-.294,1.616,1.616,0,0,1,.036-.318,4.4,4.4,0,0,1,.575-.048h.1a.7.7,0,0,1,.546.216A.856.856,0,0,1,35.472,3.837ZM34.379,1.881a.986.986,0,0,1-.084-.438.837.837,0,0,1,.084-.414.853.853,0,0,1,.511-.12.8.8,0,0,1,.5.12.837.837,0,0,1,.084.414.986.986,0,0,1-.084.438A.707.707,0,0,1,34.9,2,.874.874,0,0,1,34.379,1.881Z"/>
<path class="cls-1" d="M49.139,4.809V7.833a2.871,2.871,0,0,0,.12,1,.7.7,0,0,1-.517.18q-.42,0-.42-.54V5.085a2.2,2.2,0,0,0-.191-1.074.747.747,0,0,0-.7-.318,2.332,2.332,0,0,0-1.044.27,2.7,2.7,0,0,0-.888.69V8.937a2.324,2.324,0,0,1-.816,0V3.093l.072-.072h.312a.384.384,0,0,1,.348.132.831.831,0,0,1,.085.432v.24a2.983,2.983,0,0,1,.966-.636,2.908,2.908,0,0,1,1.134-.24,1.335,1.335,0,0,1,1.17.516A2.272,2.272,0,0,1,49.139,4.809Z"/>
<path class="cls-1" d="M40.075,3.555a2.364,2.364,0,1,1-2.364,2.364,2.367,2.367,0,0,1,2.364-2.364m0-.8a3.164,3.164,0,1,0,3.164,3.164,3.164,3.164,0,0,0-3.164-3.164Z"/>
<path class="cls-1" d="M25.315,8.285a2.349,2.349,0,0,1-2.04-1.18,2.335,2.335,0,0,1,0-2.36,2.344,2.344,0,0,1,4.08,0l.7-.4a3.16,3.16,0,1,0,0,3.16l-.7-.4A2.35,2.35,0,0,1,25.315,8.285Z"/>
<g>
<path class="cls-1" d="M17.87,8.285a2.365,2.365,0,1,1,2.36-2.37h.8a3.162,3.162,0,0,0-5.9-1.58,3.018,3.018,0,0,0-.43,1.58,3.165,3.165,0,0,0,5.91,1.58l-.69-.39A2.378,2.378,0,0,1,17.87,8.285Z"/>
<polygon class="cls-1" points="15.286 5.718 20.514 5.261 21.03 5.915 15.347 6.412 15.286 5.718"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -2,6 +2,7 @@
@import url('https://fonts.googleapis.com/css?family=Alegreya'); @import url('https://fonts.googleapis.com/css?family=Alegreya');
/* Base Application Styles */ /* Base Application Styles */
body { body {
color: #fff; color: #fff;
background: #222; background: #222;
@ -23,7 +24,7 @@ body {
left: 0; left: 0;
z-index: 1000; z-index: 1000;
display: block; display: block;
padding: 1em; padding: 0px !important;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5; background-color: #f5f5f5;
@ -34,6 +35,8 @@ body {
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
font-size: 1.2em; font-size: 1.2em;
background-color: #ffcc00;
padding: 15px;
} }
.header img { .header img {
@ -42,11 +45,12 @@ body {
.navigation { .navigation {
margin-top: 1em; margin-top: 1em;
padding: 15px;
} }
ul { ul {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0px;
} }
li { li {
@ -54,30 +58,29 @@ body {
} }
li .number { li .number {
color: #666;
display: inline-block; display: inline-block;
width: 1.1em; width: 1.1em;
} }
li a { li a {
/*color: #d30d09;*/ color: #333333;
color: #666;
display: block; display: block;
padding: 0.5em 1em; padding: 0.5em 1em;
margin: 0.1em 0; margin: 0.1em 0;
} }
li a:hover { li a:hover {
color: #000; color: #000;
background: #e9e9e9; background: #e9e9e9;
text-decoration: none; text-decoration: none;
} }
li a.active { li a.active {
background: #fff; background: #333333;
text-decoration: none; text-decoration: none;
color: #d30d09; color: #ffcc00;
} }
li a.active:hover { li a.active:hover {
color: #d30d09; color: #ffcc00;
} }
li a.disabled { li a.disabled {
color: #666; color: #666;
@ -92,6 +95,7 @@ body {
li .checkmark { li .checkmark {
font-size: 1.3em; font-size: 1.3em;
margin-right: -10px; margin-right: -10px;
color: #ffcc00;
} }
hr { hr {
@ -122,9 +126,9 @@ body {
margin-top: 0; margin-top: 0;
margin-bottom: 1em; margin-bottom: 1em;
padding-bottom: 0.5em; padding-bottom: 0.5em;
border-bottom: 2px dotted #d30d09; border-bottom: 2px dotted #ffcc00;
font-size: 2.5em; font-size: 2.5em;
color: #d30d09; color: #ffcc00;
font-family: 'Alegreya', serif; font-family: 'Alegreya', serif;
} }
@ -276,13 +280,14 @@ body {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 70px; height: 130px;
background: rgba(0,0,0,0.7); background: rgba(0,0,0,0.7);
border-radius: 5px; border-radius: 5px;
border: 3px solid #aaa; border: 3px solid #aaa;
padding: 0.5em; padding: 0.5em;
color: white; color: white;
font-family: Consolas, "Courier New", monospace; font-family: Consolas, "Courier New", monospace;
overflow: auto;
} }
.telemetry-console .date { .telemetry-console .date {
@ -363,3 +368,123 @@ body {
padding: 15px; padding: 15px;
} }
} }
/* Report page */
.report-page {
font-size: 1.2em;
border: 1px solid #fff;
padding: 2em;
-webkit-box-shadow: 1px 1px 7px -1px #ccc;
box-shadow: 1px 1px 7px -1px #ccc;
}
.report-page h1 {
margin-top: 30px;
}
.report-page h3 {
margin-top: 20px;
}
.report-page h4 {
margin-top: 20px;
}
.report-page ul {
list-style: disc;
padding-left: 40px;
}
.report-page li {
overflow: visible;
}
.report-page li a {
display: inline;
padding: 0em;
}
/* Print report styling */
@media print {
.sidebar {
display: none;
}
.no-print {
display: none;
}
.force-print {
display: block !important;
}
.pie-chart {
width: 100px;
}
.label {
padding: 2px 6px;
border: 1px solid #000;
color: #fff !important;
display: inline !important;
font-size: 75% !important;
font-weight: bold !important;
line-height: 1 !important;
text-align: center !important;
white-space: nowrap !important;
vertical-align: baseline !important;
border-radius: .25em !important;
}
.alert {
padding: 15px !important;
margin-bottom: 20px !important;
border: 1px solid transparent !important;
border-radius: 4px !important;
}
.alert-danger {
color:#a94442 !important;
background-color:#f2dede !important;
border-color:#ebccd1 !important;
}
.alert-success {
color:#3c763d !important;
background-color:#dff0d8 !important;
border-color:#d6e9c6 !important;
}
.alert-info {
color:#31708f !important;
background-color:#d9edf7 !important;
border-color:#bce8f1 !important;
}
.label-default {
background-color: #777 !important;
}
.label-primary {
background-color: #337ab7 !important;
}
.label-success {
background-color: #5cb85c !important;
}
.label-info {
background-color: #5bc0de !important;
}
.label-warning {
background-color: #f0ad4e !important;
}
.label-danger {
background-color: #d9534f !important;
}
}

View File

@ -4,10 +4,9 @@ import sys
import array import array
import struct import struct
import ipaddress
from netifaces import interfaces, ifaddresses, AF_INET from netifaces import interfaces, ifaddresses, AF_INET
from cc.database import mongo
__author__ = 'Barak' __author__ = 'Barak'
@ -56,3 +55,11 @@ def local_ip_addresses():
addresses = ifaddresses(interface).get(AF_INET, []) addresses = ifaddresses(interface).get(AF_INET, [])
ip_list.extend([link['addr'] for link in addresses if link['addr'] != '127.0.0.1']) ip_list.extend([link['addr'] for link in addresses if link['addr'] != '127.0.0.1'])
return ip_list return ip_list
def get_subnets():
subnets = []
for interface in interfaces():
addresses = ifaddresses(interface).get(AF_INET, [])
subnets.extend([ipaddress.ip_interface(link['addr'] + '/' + link['netmask']).network for link in addresses if link['addr'] != '127.0.0.1'])
return subnets

View File

@ -2,16 +2,17 @@
MONKEY_FOLDER=/var/monkey_island MONKEY_FOLDER=/var/monkey_island
INSTALLATION_FOLDER=/var/monkey_island/installation INSTALLATION_FOLDER=/var/monkey_island/installation
PYTHON_FOLDER=/var/monkey_island/bin/python
cp -f ${MONKEY_FOLDER}/monkey.sh /usr/bin/monkey cp -f ${MONKEY_FOLDER}/monkey.sh /usr/bin/monkey
chmod 755 /usr/bin/monkey chmod 755 /usr/bin/monkey
# Fix dependency bug # Prepare python virtualenv
pip uninstall -y bson pip2 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER
virtualenv -p python2.7 ${PYTHON_FOLDER}
# install pip requirements # install pip requirements
pip install -r $MONKEY_FOLDER/pip_requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER ${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/pip_requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
# remove installation folder and unnecessary files # remove installation folder and unnecessary files
rm -rf ${INSTALLATION_FOLDER} rm -rf ${INSTALLATION_FOLDER}

View File

@ -8,4 +8,6 @@ rm -f /etc/init/monkey-mongo.conf
[ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service [ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service
[ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service [ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service
rm -r -f /var/monkey_island
exit 0 exit 0

View File

@ -9,4 +9,7 @@ flask
Flask-Pymongo Flask-Pymongo
Flask-Restful Flask-Restful
jsonschema jsonschema
netifaces netifaces
ipaddress
enum34
virtualenv

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
cd /var/monkey_island/cc cd /var/monkey_island/cc
python main.py /var/monkey_island/bin/python/bin/python main.py

View File

@ -9,4 +9,6 @@ flask
Flask-Pymongo Flask-Pymongo
Flask-Restful Flask-Restful
jsonschema jsonschema
netifaces netifaces
ipaddress
enum34

View File

@ -0,0 +1 @@
xcopy %1 %2

View File

@ -1,3 +1,18 @@
bin\openssl\openssl.exe genrsa -out cc\server.key 1024 @echo off
bin\openssl\openssl.exe req -new -config bin\openssl\openssl.cfg -key cc\server.key -out cc\server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
bin\openssl\openssl.exe x509 -req -days 366 -in cc\server.csr -signkey cc\server.key -out cc\server.crt IF [%1] == [] (
set mydir=%cd%\
) ELSE (
set mydir=%~1%
)
echo Monkey Island folder: %mydir%
SET OPENSSL_CONF=%mydir%bin\openssl\openssl.cfg
copy "%mydir%windows\openssl.cfg" "%mydir%bin\openssl\openssl.cfg"
@echo on
"%mydir%bin\openssl\openssl.exe" genrsa -out "%mydir%cc\server.key" 1024
"%mydir%bin\openssl\openssl.exe" req -new -config "%mydir%bin\openssl\openssl.cfg" -key "%mydir%cc\server.key" -out "%mydir%cc\server.csr" -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
"%mydir%bin\openssl\openssl.exe" x509 -req -days 366 -in "%mydir%cc\server.csr" -signkey "%mydir%cc\server.key" -out "%mydir%cc\server.crt"

View File

@ -0,0 +1,350 @@
#
# OpenSSL example configuration file.
# This is mostly being used for generation of certificate requests.
#
# This definition stops the following lines choking if HOME isn't
# defined.
HOME = .
RANDFILE = $ENV::HOME/.rnd
# Extra OBJECT IDENTIFIER info:
#oid_file = $ENV::HOME/.oid
oid_section = new_oids
# To use this configuration file with the "-extfile" option of the
# "openssl x509" utility, name here the section containing the
# X.509v3 extensions to use:
# extensions =
# (Alternatively, use a configuration file that has only
# X.509v3 extensions in its main [= default] section.)
[ new_oids ]
# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
# Add a simple OID like this:
# testoid1=1.2.3.4
# Or use config file substitution like this:
# testoid2=${testoid1}.5.6
# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7
####################################################################
[ ca ]
default_ca = CA_default # The default ca section
####################################################################
[ CA_default ]
dir = ./demoCA # Where everything is kept
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
#unique_subject = no # Set to 'no' to allow creation of
# several ctificates with same subject.
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem# The private key
RANDFILE = $dir/private/.rand # private random number file
x509_extensions = usr_cert # The extentions to add to the cert
# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
# Extension copying option: use with caution.
# copy_extensions = copy
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
# crl_extensions = crl_ext
default_days = 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md = default # use public key default MD
preserve = no # keep passed DN ordering
# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_match
# For the CA policy
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert
# Passwords for private keys if not present they will be prompted for
# input_password = secret
# output_password = secret
# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
# utf8only: only UTF8Strings (PKIX recommendation after 2004).
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
string_mask = utf8only
# req_extensions = v3_req # The extensions to add to a certificate request
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = AU
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Some-State
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
0.organizationName_default = Internet Widgits Pty Ltd
# we can do this but it is not needed normally :-)
#1.organizationName = Second Organization Name (eg, company)
#1.organizationName_default = World Wide Web Pty Ltd
organizationalUnitName = Organizational Unit Name (eg, section)
#organizationalUnitName_default =
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
# SET-ex3 = SET extension number 3
[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName = An optional company name
[ usr_cert ]
# These extensions are added when 'ca' signs a request.
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
# nsCertType = server
# For an object signing certificate this would be used.
# nsCertType = objsign
# For normal client use this is typical
# nsCertType = client, email
# and for everything including object signing:
# nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# This will be displayed in Netscape's comment listbox.
nsComment = "OpenSSL Generated Certificate"
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName
# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
# Extensions for a typical CA
# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
# This is what PKIX recommends but some broken software chokes on critical
# extensions.
#basicConstraints = critical,CA:true
# So we do this instead.
basicConstraints = CA:true
# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign
# Some might want this also
# nsCertType = sslCA, emailCA
# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
# issuerAltName=issuer:copy
# DER hex encoding of an extension: beware experts only!
# obj=DER:02:03
# Where 'obj' is a standard or added object
# You can even override a supported extension:
# basicConstraints= critical, DER:30:03:01:01:FF
[ crl_ext ]
# CRL extensions.
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always
[ proxy_cert_ext ]
# These extensions should be added when creating a proxy certificate
# This goes against PKIX guidelines but some CAs do it and some software
# requires this to avoid interpreting an end user certificate as a CA.
basicConstraints=CA:FALSE
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
# nsCertType = server
# For an object signing certificate this would be used.
# nsCertType = objsign
# For normal client use this is typical
# nsCertType = client, email
# and for everything including object signing:
# nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# This will be displayed in Netscape's comment listbox.
nsComment = "OpenSSL Generated Certificate"
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move
# Copy subject details
# issuerAltName=issuer:copy
#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
#nsBaseUrl
#nsRevocationUrl
#nsRenewalUrl
#nsCaPolicyUrl
#nsSslServerName
# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
####################################################################
[ tsa ]
default_tsa = tsa_config1 # the default TSA section
[ tsa_config1 ]
# These are used by the TSA reply generation only.
dir = ./demoCA # TSA root directory
serial = $dir/tsaserial # The current serial number (mandatory)
crypto_device = builtin # OpenSSL engine to use for signing
signer_cert = $dir/tsacert.pem # The TSA signing certificate
# (optional)
certs = $dir/cacert.pem # Certificate chain to include in reply
# (optional)
signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
default_policy = tsa_policy1 # Policy if request did not specify it
# (optional)
other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
digests = md5, sha1 # Acceptable message digests (mandatory)
accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
clock_precision_digits = 0 # number of digits after dot. (optional)
ordering = yes # Is ordering defined for timestamps?
# (optional, default: no)
tsa_name = yes # Must the TSA name be included in the reply?
# (optional, default: no)
ess_cert_id_chain = no # Must the ESS cert id chain be included?
# (optional, default: no)

View File

@ -0,0 +1 @@
del %1

View File

@ -1,3 +1,4 @@
if not exist db mkdir db if not exist db mkdir db
start windows\run_mongodb.bat start windows\run_mongodb.bat
start windows\run_cc.bat start windows\run_cc.bat
start https://localhost:5000