Merge pull request #79 from guardicore/feature/report-backend

New feature, security report
This commit is contained in:
Daniel Goldberg 2018-01-16 19:03:35 +02:00 committed by GitHub
commit 5469dbf981
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1674 additions and 118 deletions

View File

@ -15,7 +15,7 @@ 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.resources.telemetry_feed import TelemetryFeed
from cc.services.config import ConfigService from cc.services.config import ConfigService
@ -89,6 +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/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
return app return app

View File

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

View File

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

View File

@ -53,6 +53,7 @@ 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 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'])
@ -61,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:
@ -120,6 +123,8 @@ class Monkey(flask_restful.Resource):
node_id = existing_node["_id"] node_id = existing_node["_id"]
for edge in mongo.db.edge.find({"to": node_id}): for edge in mongo.db.edge.find({"to": node_id}):
mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}}) mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}})
for creds in existing_node['creds']:
NodeService.add_credentials_to_monkey(new_monkey_id, creds)
mongo.db.node.remove({"_id": node_id}) mongo.db.node.remove({"_id": node_id})
return {"id": new_monkey_id} return {"id": new_monkey_id}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,48 +2,10 @@ import React from 'react';
import {Col} from 'react-bootstrap'; import {Col} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import PreviewPane from 'components/preview-pane/PreviewPane'; import PreviewPane from 'components/map/preview-pane/PreviewPane';
import {ReactiveGraph} from '../reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {ModalContainer, ModalDialog} from 'react-modal-dialog'; import {ModalContainer, ModalDialog} from 'react-modal-dialog';
import {options, edgeGroupToColor} from 'components/map/MapOptions';
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let getGroupsOptions = () => {
let groupOptions = {};
for (let groupName of groupNames) {
groupOptions[groupName] =
{
shape: 'image',
size: 50,
image: require('../../images/nodes/' + groupName + '.png')
};
}
return groupOptions;
};
let options = {
autoResize: true,
layout: {
improvedLayout: false
},
edges: {
width: 2,
smooth: {
type: 'curvedCW'
}
},
physics: {
barnesHut: {
gravitationalConstant: -120000,
avoidOverlap: 0.5
},
minVelocity: 0.75
},
groups: getGroupsOptions()
};
class MapPageComponent extends React.Component { class MapPageComponent extends React.Component {
constructor(props) { constructor(props) {
@ -63,20 +25,6 @@ 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.timedEvents, 1000); this.interval = setInterval(this.timedEvents, 1000);
@ -96,7 +44,7 @@ class MapPageComponent extends React.Component {
.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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -46,7 +46,7 @@ body {
ul { ul {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0px;
} }
li { li {
@ -66,6 +66,7 @@ body {
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;
@ -364,3 +365,123 @@ body {
padding: 15px; padding: 15px;
} }
} }
/* Report page */
.report-page {
font-size: 1.2em;
border: 1px solid #fff;
padding: 2em;
-webkit-box-shadow: 1px 1px 7px -1px #ccc;
box-shadow: 1px 1px 7px -1px #ccc;
}
.report-page h1 {
margin-top: 30px;
}
.report-page h3 {
margin-top: 20px;
}
.report-page h4 {
margin-top: 20px;
}
.report-page ul {
list-style: disc;
padding-left: 40px;
}
.report-page li {
overflow: visible;
}
.report-page li a {
display: inline;
padding: 0em;
}
/* Print report styling */
@media print {
.sidebar {
display: none;
}
.no-print {
display: none;
}
.force-print {
display: block !important;
}
.pie-chart {
width: 100px;
}
.label {
padding: 2px 6px;
border: 1px solid #000;
color: #fff !important;
display: inline !important;
font-size: 75% !important;
font-weight: bold !important;
line-height: 1 !important;
text-align: center !important;
white-space: nowrap !important;
vertical-align: baseline !important;
border-radius: .25em !important;
}
.alert {
padding: 15px !important;
margin-bottom: 20px !important;
border: 1px solid transparent !important;
border-radius: 4px !important;
}
.alert-danger {
color:#a94442 !important;
background-color:#f2dede !important;
border-color:#ebccd1 !important;
}
.alert-success {
color:#3c763d !important;
background-color:#dff0d8 !important;
border-color:#d6e9c6 !important;
}
.alert-info {
color:#31708f !important;
background-color:#d9edf7 !important;
border-color:#bce8f1 !important;
}
.label-default {
background-color: #777 !important;
}
.label-primary {
background-color: #337ab7 !important;
}
.label-success {
background-color: #5cb85c !important;
}
.label-info {
background-color: #5bc0de !important;
}
.label-warning {
background-color: #f0ad4e !important;
}
.label-danger {
background-color: #d9534f !important;
}
}

View File

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

View File

@ -10,4 +10,6 @@ Flask-Pymongo
Flask-Restful Flask-Restful
jsonschema jsonschema
netifaces netifaces
ipaddress
enum34
virtualenv virtualenv

View File

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