forked from p34709852/monkey
commit
7f5734ac67
|
@ -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]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'))
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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()
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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 Monkey’s
|
||||||
|
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 Monkey’s
|
||||||
|
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;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 |
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
|
@ -9,4 +9,7 @@ flask
|
||||||
Flask-Pymongo
|
Flask-Pymongo
|
||||||
Flask-Restful
|
Flask-Restful
|
||||||
jsonschema
|
jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
|
ipaddress
|
||||||
|
enum34
|
||||||
|
virtualenv
|
|
@ -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
|
|
@ -9,4 +9,6 @@ flask
|
||||||
Flask-Pymongo
|
Flask-Pymongo
|
||||||
Flask-Restful
|
Flask-Restful
|
||||||
jsonschema
|
jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
|
ipaddress
|
||||||
|
enum34
|
|
@ -0,0 +1 @@
|
||||||
|
xcopy %1 %2
|
|
@ -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"
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
||||||
|
del %1
|
|
@ -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
|
Loading…
Reference in New Issue