import os from flask import Flask, request, abort, send_from_directory from flask.ext import restful from flask.ext.pymongo import PyMongo from flask import make_response import bson.json_util import json from datetime import datetime import dateutil.parser MONKEY_DOWNLOADS = [ { 'type': 'linux', 'machine': 'x86_64', 'filename': 'monkey-linux-64', }, { 'type': 'linux', 'machine': 'i686', 'filename': 'monkey-linux-32', }, { 'type': 'linux', 'filename': 'monkey-linux-32', }, { 'type': 'windows', 'machine': 'x86', 'filename': 'monkey-linux-32.exe', }, { 'type': 'windows', 'machine': 'amd64', 'filename': 'monkey-windows-64.exe', }, { 'type': 'windows', 'filename': 'monkey-windows-32.exe', }, ] MONGO_URL = os.environ.get('MONGO_URL') if not MONGO_URL: MONGO_URL = "mongodb://localhost:27017/monkeyisland" app = Flask(__name__) app.config['MONGO_URI'] = MONGO_URL mongo = PyMongo(app) class Monkey(restful.Resource): def get(self, **kw): guid = request.args.get('guid') timestamp = request.args.get('timestamp') if guid: return mongo.db.monkey.find_one_or_404({"guid": guid}) else: result = {'timestamp': datetime.now().isoformat()} find_filter = {} if None != timestamp: find_filter['modifytime'] = {'$gt': dateutil.parser.parse(timestamp)} result['objects'] = [x for x in mongo.db.monkey.find(find_filter)] return result 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: update['$set']['keepalive'] = datetime.now() if monkey_json.has_key('config'): 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): monkey_json = json.loads(request.data) if monkey_json.has_key('keepalive'): monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) else: monkey_json['keepalive'] = datetime.now() monkey_json['modifytime'] = datetime.now() # if new monkey, 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 {} monkey_json['config'] = monkey_json.get('config', {}) monkey_json['config'].update(new_config) else: db_config = db_monkey.get('config', {}) 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'): exploit_telem = [x for x in mongo.db.telemetry.find({'telem_type': {'$eq': 'exploit'}, 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] if 1 == len(exploit_telem): monkey_json['parent'] = exploit_telem[0].get('monkey_guid') return mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True) class Telemetry(restful.Resource): def get(self, **kw): 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... timestamp = None result = {'timestamp': datetime.now().isoformat()} find_filter = {} if monkey_guid: find_filter["monkey_guid"] = {'$eq': monkey_guid} if telem_type: find_filter["telem_type"] = {'$eq': telem_type} if timestamp: find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)} result['objects'] = [x for x in mongo.db.telemetry.find(find_filter)] return result def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() telem_id = mongo.db.telemetry.insert(telemetry_json) # 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['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) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$unset': {'tunnel_guid':''}}, upsert=True) 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) else: mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, {'$set': {'dead': False}}, upsert=True) except: pass return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) class NewConfig(restful.Resource): def get(self): config = mongo.db.config.find_one({'name': 'newconfig'}) or {} if config.has_key('name'): del config['name'] return config def post(self): config_json = json.loads(request.data) return mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) class MonkeyDownload(restful.Resource): def get(self, path): return send_from_directory('binaries', path) def post(self): host_json = json.loads(request.data) host_os = host_json.get('os') if os: result = None for download in MONKEY_DOWNLOADS: if host_os.get('type') == download.get('type') and \ host_os.get('machine') == download.get('machine'): result = download break if result: real_path = os.path.join('binaries', result['filename']) if os.path.isfile(real_path): result['size'] = os.path.getsize(real_path) return result return {} class Root(restful.Resource): def get(self): return { 'status': 'OK', 'mongo': str(mongo.db), } def normalize_obj(obj): if obj.has_key('_id') and not obj.has_key('id'): obj['id'] = obj['_id'] del obj['_id'] 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) if type(value) is dict: obj[key] = normalize_obj(value) if type(value) is list: for i in range(0,len(value)): if type(value[i]) is dict: value[i] = normalize_obj(value[i]) return obj def output_json(obj, code, headers=None): obj = normalize_obj(obj) resp = make_response(bson.json_util.dumps(obj), code) resp.headers.extend(headers or {}) return resp @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 api.add_resource(Root, '/api') api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') api.add_resource(NewConfig, '/api/config/new') api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))