From fc95dccf2222b11183b56bafd37dec3542a935a6 Mon Sep 17 00:00:00 2001 From: itsikkes Date: Sun, 24 Jul 2016 01:04:42 +0300 Subject: [PATCH] bug fixes and features added support for multi-parent, auto mark dead monkeys,; UI: get exploit details, can select edges, show num pf monkeys alive, show failed exploit attempts; some bug fixes; --- chaos_monkey/monkey.py | 5 +- monkey_island/cc/admin/ui/index.html | 25 +- monkey_island/cc/admin/ui/js/monkeys-admin.js | 251 +++++++++++++----- monkey_island/cc/main.py | 76 +++--- 4 files changed, 254 insertions(+), 103 deletions(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index be7184ad3..482211b81 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -162,6 +162,9 @@ class ChaosMonkey(object): break else: LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) + ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, + 'exploiter': exploiter.__class__.__name__}) + except Exception, exc: LOG.error("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) @@ -169,7 +172,7 @@ class ChaosMonkey(object): if successful_exploiter: self._exploited_machines.add(machine) - ControlClient.send_telemetry('exploit', {'machine': machine.__dict__, + ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__, 'exploiter': successful_exploiter.__class__.__name__}) LOG.info("Successfully propagated to %s using %s", diff --git a/monkey_island/cc/admin/ui/index.html b/monkey_island/cc/admin/ui/index.html index d1c79e1d5..c2a8eb2a1 100644 --- a/monkey_island/cc/admin/ui/index.html +++ b/monkey_island/cc/admin/ui/index.html @@ -83,8 +83,9 @@
- Num of Monkeys: ( by exploiting)
- Num of Hosts Detected:
+ Num of Monkeys: ( exploiting were done)
+ Monkyes Alive:
+ Num of Hosts Not Exploited:
Num of Tunnels Used:
@@ -132,7 +133,7 @@

- +

@@ -188,6 +189,24 @@
+ +
+
+ +
+ + + +
+
+
+ +
diff --git a/monkey_island/cc/admin/ui/js/monkeys-admin.js b/monkey_island/cc/admin/ui/js/monkeys-admin.js index b98edc16a..e009b46b7 100644 --- a/monkey_island/cc/admin/ui/js/monkeys-admin.js +++ b/monkey_island/cc/admin/ui/js/monkeys-admin.js @@ -62,12 +62,13 @@ function initAdmin() { nodes = []; edges = []; + createNodes(); createEdges(); createTunnels(); createScanned(); var data = { - nodes: createNodes(), + nodes: nodes, edges: edges }; @@ -76,7 +77,10 @@ function initAdmin() { var options = { layout: { improvedLayout: false - } + }/*, + physics: { + enabled: true + }*/ }; // Using jQuery to get the element does not work with vis.js library @@ -196,7 +200,19 @@ function updateMonkeys() { else { monkeys.push(new_monkeys[i]); - nodes.push(createMonkeyNode(new_monkeys[i])); + var exiting_scan = undefined; + for (var j=0; jFrom: " + parent.hostname + "
To: " + monkey.hostname; + edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, title: title, /*label: exploit, font: {color: 'red', size: 10, align: 'top'},*/ color: EDGE_COLOR_PARENT}); + if (removeEdge([parent.id, monkey.id, EDGE_TYPE_SCAN])) { + numOfScanLinks--; + } + numOfParentLinks++; + } } } } @@ -327,24 +387,20 @@ function createScanned() { var scan = scans[i]; var monkey = getMonkeyByGuid(scan.monkey_guid); - //Check if we already exploited this machine from another PoV, if so no point in scanning. - if (null != getMonkeyByIP(scan.data.machine.ip_addr)) { - //if so, make sure we don't already have such a node - nodes = nodes.filter(function (node) { - return (node.id != ip_addr); - }); - continue; - } + // And check if we've already added this scanned machine + var machineNode = getMonkeyByIP(scan.data.machine.ip_addr); - //And check if we've already added this scanned machine - var machineNode = getScannedByIP(scan.data.machine.ip_addr) if (null == machineNode) { - machineNode = createMachineNode(scan.data.machine); - scannedMachines.push(machineNode); - nodes.push(machineNode); + machineNode = getScannedByIP(scan.data.machine.ip_addr); + + if (null == machineNode) { + machineNode = createMachineNode(scan.data.machine); + scannedMachines.push(machineNode); + nodes.push(machineNode); + } } - if(!edgeExists([monkey.id, machineNode.id, EDGE_TYPE_SCAN])) { + if(!edgeExists([monkey.id, machineNode.id, EDGE_TYPE_SCAN]) && !edgeExists([monkey.id, machineNode.id, EDGE_TYPE_PARENT])) { edges.push({from: monkey.id, to: machineNode.id, arrows:'middle', type: EDGE_TYPE_SCAN, color: EDGE_COLOR_SCAN}); numOfScanLinks++; } @@ -372,10 +428,34 @@ function buildMonkeyDescription(monkey) { } html += " " + monkey.keepalive + "
" + - "
"; + "
"; + html += ""; + + + if (monkey.parent != null) { + html += "
" + html += ""; } return html; @@ -386,6 +466,11 @@ function updateCounters() { $('#infoNumOfHosts').html(scannedMachines.length); $('#infoNumOfParents').html(numOfParentLinks); $('#infoNumOfTunnels').html(numOfTunnelLinks); + var numOfAlive = monkeys.length; + for (var i=0;i 0) { + if ((properties.nodes.length > 0) && getMonkey(properties.nodes[0])){ onNodeSelect(properties.nodes); } else { - var content = "No selection" + var content = "Monkey not selected" $("#selectionInfo").html(content); $('#monkey-config').hide() $('#btnConfigLoad, #btnConfigUpdate').hide(); $('#monkey-enabled').hide(); telemTable.clear(); telemTable.draw(); - } - if (properties.edges.length > 0) { - onEdgeSelect(properties.edges); + if (properties.edges.length > 0) { + onEdgeSelect(properties.edges); + } } } @@ -483,33 +568,32 @@ function onNodeSelect(nodeId) { if (monkey) { htmlContent = buildMonkeyDescription(monkey); $("#monkeySearch").val(monkey.hostname); + $("#selectionInfo").html(htmlContent); + $('#monkey-config').show() + $('#btnConfigLoad, #btnConfigUpdate').show(); + + loadMonkeyConfig(); + + if (monkey.config.alive) { + $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', true, true); + } + else { + $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', false, true); + } + $('#monkey-enabled').show(); + + $.getJSON('/api/telemetry?monkey_guid=' + monkey.guid, function(json) { + telemTable.clear(); + var telemetries = json.objects; + + for (var i = 0; i < telemetries.length; i++) { + telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); + } + + telemTable.draw(); + }); } - $("#selectionInfo").html(htmlContent); - $('#monkey-config').show() - $('#btnConfigLoad, #btnConfigUpdate').show(); - - loadMonkeyConfig(); - - if (monkey.config.alive) { - $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', true); - } - else { - $("[name='chboxMonkeyEnabled']").bootstrapSwitch('state', false); - } - $('#monkey-enabled').show(); - - $.getJSON('/api/telemetry?monkey_guid=' + monkey.guid, function(json) { - telemTable.clear(); - var telemetries = json.objects; - - for (var i = 0; i < telemetries.length; i++) { - telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); - } - - telemTable.draw(); - }); - network.selectNodes([nodeId]); } @@ -518,7 +602,32 @@ function onNodeSelect(nodeId) { */ function onEdgeSelect(edge) { var edge = getEdge(edge); + var monkey = getMonkey(edge.from); + if (!monkey) {return;}; + var target = undefined; + if (edge.type == 'scan') { + target = getScannedByIP(edge.to) + } + else { + target = getMonkey(edge.to) + } + + $.getJSON(jsonFileTelemetry + '?monkey_guid=' + monkey.guid, function(json) { + telemTable.clear(); + var telemetries = json.objects; + + for (var i = 0; i < telemetries.length; i++) { + var telem = telemetries[i] + if (telem.telem_type == 'scan' || telem.telem_type == 'exploit') { + if (((edge.type == 'scan') && (telem.data.machine.ip_addr == target.id)) || + ((edge.type == 'parent') && (0 <= $.inArray(telem.data.machine.ip_addr, target.ip_addresses)))) { + telemTable.row.add([telemetries[i].timestamp, telemetries[i].telem_type, JSON.stringify(telemetries[i].data)]); + } + } + } + telemTable.draw(); + }); } function toggleMonkeyEnabled(event, state) { @@ -728,24 +837,24 @@ function getMonkeyByGuid(guid) { if (monkeys[i].guid == guid) { return monkeys[i]; } - } + } + return null; } function getMonkeyByIP(ip) { - for (var i = 0; i < monkeys.length; i++) { + for (var i = 0; i < monkeys.length; i++) { var monkey = monkeys[i]; - for (var j = 0; j< monkey.ip_addresses; j++) { - if (monkeys[i].ip == ip) { - return monkeys[i]; + for (var j = 0; j< monkey.ip_addresses.length; j++) { + if (monkey.ip_addresses[j] == ip) { + return monkey; } } } return null; } -function getScannedByIP(ip) -{ - for (var i = 0; i < scannedMachines.length; i++) { +function getScannedByIP(ip) { + for (var i = 0; i < scannedMachines.length; i++) { var machine = scannedMachines[i]; if (machine.id == ip) { return machine @@ -801,13 +910,23 @@ function edgeExists(link) { var to = edges[i].to; var type = edges[i].type; if (from == link[0] && to == link[1] && type == link[2]) { - return true; + return edges[i]; } } } - - +function removeEdge(link) { + for (var i = 0; i < edges.length; i++) { + var from = edges[i].from; + var to = edges[i].to; + var type = edges[i].type; + if (from == link[0] && to == link[1] && type == link[2]) { + edges.splice(i, 1); + return true; + } + } + return false; +} /** * Clears the value in the local storage diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index 97799c303..9dfe6a78b 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -5,7 +5,7 @@ from flask.ext.pymongo import PyMongo from flask import make_response import bson.json_util import json -from datetime import datetime +from datetime import datetime, timedelta import dateutil.parser MONKEY_DOWNLOADS = [ @@ -50,6 +50,7 @@ mongo = PyMongo(app) class Monkey(restful.Resource): def get(self, guid=None, **kw): + update_dead_monkeys() # refresh monkeys status if not guid: guid = request.args.get('guid') timestamp = request.args.get('timestamp') @@ -59,7 +60,7 @@ class Monkey(restful.Resource): else: result = {'timestamp': datetime.now().isoformat()} find_filter = {} - if None != timestamp: + if timestamp is not None: find_filter['modifytime'] = {'$gt': dateutil.parser.parse(timestamp)} result['objects'] = [x for x in mongo.db.monkey.find(find_filter)] return result @@ -67,7 +68,7 @@ class Monkey(restful.Resource): def patch(self, guid): monkey_json = json.loads(request.data) update = {"$set": {'modifytime': datetime.now()}} - + if monkey_json.has_key('keepalive'): update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) else: @@ -76,7 +77,7 @@ class Monkey(restful.Resource): update['$set']['config'] = monkey_json['config'] if monkey_json.has_key('tunnel'): update['$set']['tunnel'] = monkey_json['tunnel'] - + return mongo.db.monkey.update({"guid": guid}, update, upsert=False) def post(self, **kw): @@ -88,7 +89,7 @@ class Monkey(restful.Resource): monkey_json['modifytime'] = datetime.now() - # if new monkey, 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"]}) if not db_monkey: new_config = mongo.db.config.find_one({'name': 'newconfig'}) or {} @@ -99,18 +100,31 @@ class Monkey(restful.Resource): if db_config.has_key('current_server'): del db_config['current_server'] monkey_json.get('config', {}).update(db_config) - - if not monkey_json.has_key('parent') and db_monkey.get('parent'): - monkey_json['parent'] = db_monkey.get('parent') # try to find new monkey parent parent = monkey_json.get('parent') - if (not parent or parent == monkey_json.get('guid')) and monkey_json.has_key('ip_addresses'): + parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run + if parent and parent != monkey_json.get('guid'): # current parent is known exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.machine.ip_addr': - {'$in': monkey_json['ip_addresses']}})] + mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}, + 'monkey_guid': {'$eq': parent}})] if 1 == len(exploit_telem): - monkey_json['parent'] = exploit_telem[0].get('monkey_guid') + parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + else: + parent_to_add = (parent, None) + elif (not parent or parent == monkey_json.get('guid')) and monkey_json.has_key('ip_addresses'): + exploit_telem = [x for x in + mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.result': {'$eq': True}, + 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] + + if 1 == len(exploit_telem): + parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + + if not db_monkey: + monkey_json['parent'] = [parent_to_add] + else: + monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add] return mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, @@ -122,7 +136,7 @@ class Telemetry(restful.Resource): monkey_guid = request.args.get('monkey_guid') telem_type = request.args.get('telem_type') timestamp = request.args.get('timestamp') - if "null" == timestamp: #special case to avoid ugly JS code... + if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None result = {'timestamp': datetime.now().isoformat()} @@ -146,37 +160,26 @@ class Telemetry(restful.Resource): # update exploited monkeys parent try: - if telemetry_json.get('telem_type') == 'exploit': - update_parent = [] - for monkey in mongo.db.monkey.find({"ip_addresses": - {'$elemMatch': - {'$eq': telemetry_json['data']['machine']['ip_addr']}}}): - parent = monkey.get('parent') - if parent == monkey.get('guid') or not parent: - update_parent.append(monkey) - if 1 == len(update_parent): - update_parent[0]['parent'] = telemetry_json['monkey_guid'] - mongo.db.monkey.update({"guid": update_parent[0]['guid']}, {"$set": update_parent[0]}, upsert=False) - elif telemetry_json.get('telem_type') == 'tunnel': + if telemetry_json.get('telem_type') == 'tunnel': if telemetry_json['data']: host = telemetry_json['data'].split(":")[-2].replace("//", "") tunnel_host = mongo.db.monkey.find_one({"ip_addresses": host}) mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'tunnel_guid': tunnel_host.get('guid')}}, - upsert=True) + upsert=False) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, - {'$unset': {'tunnel_guid':''}}, - upsert=True) + {'$unset': {'tunnel_guid': ''}}, + upsert=False) elif telemetry_json.get('telem_type') == 'state': if telemetry_json['data']['done']: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'dead': True}}, - upsert=True) + upsert=False) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'dead': False}}, - upsert=True) + upsert=False) except: pass @@ -232,15 +235,15 @@ def normalize_obj(obj): obj['id'] = obj['_id'] del obj['_id'] - for key,value in obj.items(): + for key, value in obj.items(): if type(value) is bson.objectid.ObjectId: obj[key] = str(value) if type(value) is datetime: - obj[key] = str(value) + obj[key] = str(value) if type(value) is dict: obj[key] = normalize_obj(value) if type(value) is list: - for i in range(0,len(value)): + for i in range(0, len(value)): if type(value[i]) is dict: value[i] = normalize_obj(value[i]) return obj @@ -253,10 +256,17 @@ def output_json(obj, code, headers=None): return resp +def update_dead_monkeys(): + mongo.db.monkey.update( + {'keepalive': {'$lte': datetime.now() - timedelta(minutes=10)}, 'dead': {'$ne': True}}, + {'$set': {'dead': True, 'modifytime': datetime.now()}}, upsert=False) + + @app.route('/admin/') def send_admin(path): return send_from_directory('admin/ui', path) + DEFAULT_REPRESENTATIONS = {'application/json': output_json} api = restful.Api(app) api.representations = DEFAULT_REPRESENTATIONS