This commit is contained in:
acepace 2016-07-25 17:44:37 +03:00
commit 3cd0d5818a
5 changed files with 325 additions and 114 deletions

View File

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

View File

@ -1,4 +1,45 @@
dependencies:
sudo pip install pyVmomi
sudo pip install celery
sudo pip install -U celery[mongodb]
How to install Monkey Business server:
---------------- On Linux ----------------:
1. Create the following directories:
sudo mkdir /var/monkey_business
sudo chmod 777 /var/monkey_business
mkdir -p /var/monkey_business/bin/mongodb
mkdir -p /var/monkey_business/db
mkdir -p /var/monkey_business/cc
2. Install the following packages:
sudo pip install flask
sudo pip install Flask-Pymongo
sudo pip install Flask-Restful
sudo pip install python-dateutil
sudo pip install pyVmomi
sudo pip install celery
sudo pip install -U celery[mongodb]
4. Download MongoDB and extract it to /var/monkey_business/bin/mongodb
for debian64 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian71-3.0.7.tgz
for ubuntu64 14.10 - https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1410-clang-3.0.7.tgz
find more at - https://www.mongodb.org/downloads#production
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_business/bin/mongodb
(make sure the content of the mongo folder is in this directory, meaning this path exists:
/var/monkey_business/bin/mongodb/bin)
5. install OpenSSL
sudo apt-get install openssl
6. Generate SSL Certificate, Run create_certificate.sh (located under /linux)
7. Copy monkey business server to /var/monkey_business:
cp -r [monkey_island_source]/cc /var/monkey_business/
How to run:
1. run run.sh
* This performs:
DB startup:
/var/monkey_business/bin/mongodb/bin/mongod --dbpath db --fork --logpath db.log
Jobs worker startup:
nohup celery -A tasks_manager worker --loglevel=info
Main Web Server startup:
nohup python main.py

View File

@ -83,8 +83,9 @@
</div>
<div id="info" class="panel-body panel-collapse collapse in">
<div>
Num of Monkeys: <label id="infoNumOfMonkeys">0</label> (<label id="infoNumOfParents">0</label> by exploiting)<br/>
Num of Hosts Detected: <label id="infoNumOfHosts">0</label><br/>
Num of Monkeys: <label id="infoNumOfMonkeys">0</label> (<label id="infoNumOfParents">0</label> exploiting were done)<br/>
Monkyes Alive: <label id="infoNumOfAlive">0</label><br/>
Num of Hosts Not Exploited: <label id="infoNumOfHosts">0</label><br/>
Num of Tunnels Used: <label id="infoNumOfTunnels">0</label><br/>
</div>
<div>
@ -132,7 +133,7 @@
</div>
<hr>
<div id="selectionInfo">
<label>No selection</label>
<label>Monkey not selected</label>
</div>
<hr>
<div class="panel panel-default">
@ -188,6 +189,24 @@
</div>
<!-- /.Config section -->
<!-- Config section -->
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<a href="#reset" data-toggle="collapse">Reset Database</a>
</div>
<div id="reset" style="overflow: visible" class="panel-body panel-collapse collapse" aria-expanded="true">
<span class="input-group-btn">
<button id="btnResetDB" class="btn btn-default" type="button"
onclick="resetDB()" style="margin-top:-4px">
Reset Database
</button>
</span>
</div>
</div>
</div>
<!-- /.Config section -->
</div>
</body>

View File

@ -62,12 +62,13 @@ function initAdmin() {
nodes = [];
edges = [];
createNodes();
createEdges();
createTunnels();
createScanned();
var data = {
nodes: createNodes(),
nodes: nodes,
edges: edges
};
@ -196,8 +197,19 @@ function updateMonkeys() {
else
{
monkeys.push(new_monkeys[i]);
nodes.push(createMonkeyNode(new_monkeys[i]));
updateCounters();
var exiting_scan = undefined;
for (var j=0; j<new_monkeys[i].ip_addresses.length; j++) {
exiting_scan = getScannedByIP(new_monkeys[i].ip_addresses[j]);
if (exiting_scan != undefined) {
break;
}
}
if (exiting_scan == undefined) {
nodes.push(createMonkeyNode(new_monkeys[i]));
}
else {
convertScanNodeToMonkey(exiting_scan, new_monkeys[i]);
}
}
}
@ -208,6 +220,7 @@ function updateMonkeys() {
refreshDrawing();
}
createScanned();
updateCounters();
});
}
@ -238,9 +251,16 @@ function createMonkeyNode(monkey) {
}
img = ICONS_DIR + img + ICONS_EXT;
if (monkey.parent == monkey.guid) {
if (monkey.parent == null) {
font = { color: 'red' };
}
else {
for (var i=0; i<monkey.parent.length; i++) {
if (monkey.parent[i][1] == null) {
font = { color: 'red' };
}
}
}
return {
'id': monkey.id,
@ -283,15 +303,52 @@ function createMachineNode(machine) {
};
}
function convertScanNodeToMonkey(scanned, monkey) {
var monNode = createMonkeyNode(monkey);
nodes.push(monNode);
// move edges to new node
for (var i = 0; i < edges.length; i++) {
if (edges[i].to == scanned.id) {
edges[i].to = monNode.id;
}
if (edges[i].from == scanned.id) {
edges[i].from = monNode.id;
}
}
for (var i=0; i<scannedMachines.length; i++) {
if (scannedMachines[i].id == scanned.id) {
scannedMachines.splice(i, 1);
break;
}
}
for (var i=0; i<nodes.length; i++) {
if (nodes[i].id == scanned.id) {
nodes.splice(i, 1);
break;
}
}
}
function createEdges() {
for (var i = 0; i < monkeys.length; i++) {
var monkey = monkeys[i];
if(monkey.parent != monkey.guid) {
var parent = getMonkeyByGuid(monkey.parent);
if(parent && !edgeExists([parent.id, monkey.id, EDGE_TYPE_PARENT])) {
edges.push({from: parent.id, to: monkey.id, arrows:'middle', type: EDGE_TYPE_PARENT, color: EDGE_COLOR_PARENT});
numOfParentLinks++;
if (monkey.parent == null) { continue; };
for (var j=0; j<monkey.parent.length; j++) {
if(monkey.parent[j][0] != monkey.guid) {
var parent = getMonkeyByGuid(monkey.parent[j][0]);
var exploit = monkey.parent[j][1];
if(parent && !edgeExists([parent.id, monkey.id, EDGE_TYPE_PARENT])) {
var title = "<center><b>" + exploit + "</b></center>From: " + parent.hostname + "<br/>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 +384,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 +425,34 @@ function buildMonkeyDescription(monkey) {
}
html +=
"<label>Last Seen:</label> " + monkey.keepalive + "</br>" +
"<label>IP Address:</label></br>";
"<label>IP Address:</label><br/>";
html += "<ul>";
for (var i = 0; i < monkey.ip_addresses.length; i++) {
html += monkey.ip_addresses[i] + "</br>"
html += "<li>" + monkey.ip_addresses[i];
}
html += "</ul>";
if (monkey.parent != null) {
html += "<label>Exploited by:</label><br/>"
html += "<ul>";
for (var i = 0; i < monkey.parent.length; i++) {
html += "<li>";
if (monkey.parent[i][0] == monkey.guid) {
html += "Manual Run<br/>";
}
else {
parent = getMonkeyByGuid(monkey.parent[i][0]);
if (!parent) { html += "Unknown Source"; continue; }
html += parent.hostname + " (";
if (monkey.parent[i][1] == null) {html += "Unknown"}
else {html += monkey.parent[i][1];}
html += ")";
}
}
html += "</ul>";
}
return html;
@ -386,6 +463,11 @@ function updateCounters() {
$('#infoNumOfHosts').html(scannedMachines.length);
$('#infoNumOfParents').html(numOfParentLinks);
$('#infoNumOfTunnels').html(numOfTunnelLinks);
var numOfAlive = monkeys.length;
for (var i=0;i<monkeys.length;i++) {
if (monkeys[i].dead) {numOfAlive--;}
}
$('#infoNumOfAlive').html(numOfAlive);
}
@ -451,22 +533,22 @@ function onDoubleClick(properties) {
*/
function onSelect(properties) {
if (properties.nodes.length > 0) {
if ((properties.nodes.length > 0) && getMonkey(properties.nodes[0])){
onNodeSelect(properties.nodes);
}
else
{
var content = "<b>No selection</b>"
var content = "<b>Monkey not selected</b>"
$("#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 +565,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 +599,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) {
@ -694,8 +800,17 @@ function resetDB() {
url : '/api?action=reset',
type : 'GET',
success : function(response, textStatus, jqXhr) {
console.log("DB was successfully reset!");
location.reload();
console.log(response);
if (response.status != 'OK') {
BootstrapDialog.show({
title: "Reset DB",
message: "The following error occured: " + response.reason
});
}
else {
console.log("DB was successfully reset!");
location.reload();
}
},
error : function(jqXHR, textStatus, errorThrown) {
// log the error to the console
@ -728,24 +843,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 +916,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

View File

@ -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
@ -220,11 +223,24 @@ class MonkeyDownload(restful.Resource):
class Root(restful.Resource):
def get(self):
return {
'status': 'OK',
'mongo': str(mongo.db),
}
def get(self, action=None):
if not action:
action = request.args.get('action')
if not action:
return {
'status': 'OK',
'mongo': str(mongo.db),
}
elif action=="reset":
mongo.db.config.drop()
mongo.db.monkey.drop()
mongo.db.telemetry.drop()
return {
'status': 'OK',
}
else:
return {'status': 'BAD',
'reason': 'unknown action'}
def normalize_obj(obj):
@ -232,15 +248,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 +269,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/<path:path>')
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