monkey/monkey_island/cc/main.py

469 lines
17 KiB
Python
Raw Normal View History

2016-08-23 00:40:38 +08:00
from __future__ import print_function # In python 2.7
2015-09-29 22:01:09 +08:00
import os
import sys
import array
import struct
from shutil import copyfile
from flask import Flask, request, abort, send_from_directory, redirect
2015-09-29 22:01:09 +08:00
from flask.ext import restful
from flask.ext.pymongo import PyMongo
from flask import make_response
import socket
2015-09-29 22:01:09 +08:00
import bson.json_util
import json
from datetime import datetime, timedelta
2015-09-29 22:01:09 +08:00
import dateutil.parser
ISLAND_PORT = 5000
2015-09-29 22:01:09 +08:00
MONKEY_DOWNLOADS = [
2015-12-02 17:18:27 +08:00
{
'type': 'linux',
'machine': 'x86_64',
'filename': 'monkey-linux-64',
},
{
'type': 'linux',
'machine': 'i686',
'filename': 'monkey-linux-32',
},
{
'type': 'linux',
'filename': 'monkey-linux-64',
2015-12-02 17:18:27 +08:00
},
{
'type': 'windows',
'machine': 'x86',
'filename': 'monkey-windows-32.exe',
2015-12-02 17:18:27 +08:00
},
{
'type': 'windows',
'machine': 'amd64',
'filename': 'monkey-windows-64.exe',
},
{
'type': 'windows',
'filename': 'monkey-windows-32.exe',
},
]
2015-09-29 22:01:09 +08:00
INITIAL_USERNAMES = ['Administrator', 'root', 'user']
INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"]
2015-09-29 22:01:09 +08:00
MONGO_URL = os.environ.get('MONGO_URL')
if not MONGO_URL:
2015-12-02 17:18:27 +08:00
MONGO_URL = "mongodb://localhost:27017/monkeyisland"
2015-09-29 22:01:09 +08:00
app = Flask(__name__)
app.config['MONGO_URI'] = MONGO_URL
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')
2015-09-29 22:01:09 +08:00
timestamp = request.args.get('timestamp')
2015-12-02 17:18:27 +08:00
if guid:
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
monkey_json['config']['exploit_user_list'] = \
map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)]))
monkey_json['config']['exploit_password_list'] = \
map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)]))
return monkey_json
2015-09-29 22:01:09 +08:00
else:
result = {'timestamp': datetime.now().isoformat()}
find_filter = {}
if timestamp is not None:
2015-09-29 22:01:09 +08:00
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):
2015-12-02 17:18:27 +08:00
monkey_json = json.loads(request.data)
update = {"$set": {'modifytime': datetime.now()}}
2016-08-23 00:40:38 +08:00
if 'keepalive' in monkey_json:
2015-10-08 18:37:38 +08:00
update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
else:
update['$set']['keepalive'] = datetime.now()
2016-08-23 00:40:38 +08:00
if 'config' in monkey_json:
2015-09-29 22:01:09 +08:00
update['$set']['config'] = monkey_json['config']
2016-08-23 00:40:38 +08:00
if 'tunnel' in monkey_json:
2015-10-08 18:37:38 +08:00
update['$set']['tunnel'] = monkey_json['tunnel']
if 'config_error' in monkey_json:
update['$set']['config_error'] = monkey_json['config_error']
return mongo.db.monkey.update({"guid": guid}, update, upsert=False)
2015-09-29 22:01:09 +08:00
def post(self, **kw):
monkey_json = json.loads(request.data)
2016-08-23 00:40:38 +08:00
if 'keepalive' in monkey_json:
2015-09-29 22:01:09 +08:00
monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
else:
monkey_json['keepalive'] = datetime.now()
monkey_json['modifytime'] = datetime.now()
# if new monkey telem, change config according to "new monkeys" config.
2015-09-29 22:01:09 +08:00
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
if not db_monkey:
2015-12-02 17:18:27 +08:00
new_config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
2015-10-08 18:37:38 +08:00
monkey_json['config'] = monkey_json.get('config', {})
2015-10-14 22:20:01 +08:00
monkey_json['config'].update(new_config)
2015-09-29 22:01:09 +08:00
else:
2015-10-08 18:37:38 +08:00
db_config = db_monkey.get('config', {})
2016-08-23 00:40:38 +08:00
if 'current_server' in db_config:
2015-10-08 18:37:38 +08:00
del db_config['current_server']
monkey_json.get('config', {}).update(db_config)
# try to find new monkey parent
parent = monkey_json.get('parent')
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.result': {'$eq': True},
'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']},
'monkey_guid': {'$eq': parent}})]
if 1 == len(exploit_telem):
parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter'))
else:
parent_to_add = (parent, None)
2016-08-23 00:40:38 +08:00
elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json:
2015-12-02 17:18:27 +08:00
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']}})]
2015-10-08 18:37:38 +08:00
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]
2015-09-29 22:01:09 +08:00
2015-10-08 18:37:38 +08:00
return mongo.db.monkey.update({"guid": monkey_json["guid"]},
2015-12-02 17:18:27 +08:00
{"$set": monkey_json},
2015-10-08 18:37:38 +08:00
upsert=True)
2015-09-29 22:01:09 +08:00
2015-09-29 22:01:09 +08:00
class Telemetry(restful.Resource):
def get(self, **kw):
2016-06-14 19:39:58 +08:00
monkey_guid = request.args.get('monkey_guid')
telem_type = request.args.get('telem_type')
2015-09-29 22:01:09 +08:00
timestamp = request.args.get('timestamp')
if "null" == timestamp: # special case to avoid ugly JS code...
2016-06-14 19:39:58 +08:00
timestamp = None
2015-09-29 22:01:09 +08:00
result = {'timestamp': datetime.now().isoformat()}
find_filter = {}
2015-12-02 17:18:27 +08:00
if monkey_guid:
2015-09-29 22:01:09 +08:00
find_filter["monkey_guid"] = {'$eq': monkey_guid}
2016-06-14 19:39:58 +08:00
if telem_type:
find_filter["telem_type"] = {'$eq': telem_type}
2015-12-02 17:18:27 +08:00
if timestamp:
2015-09-29 22:01:09 +08:00
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') == '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']},
2016-08-23 00:40:38 +08:00
{'$set': {'tunnel_guid': tunnel_host.get('guid'),
'modifytime': datetime.now()}},
upsert=False)
else:
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
{'$unset': {'tunnel_guid': ''}, 'modifytime': datetime.now()},
upsert=False)
2016-07-04 15:44:57 +08:00
elif telemetry_json.get('telem_type') == 'state':
if telemetry_json['data']['done']:
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
{'$set': {'dead': True, 'modifytime': datetime.now()}},
upsert=False)
2016-07-04 15:44:57 +08:00
else:
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
{'$set': {'dead': False, 'modifytime': datetime.now()}},
upsert=False)
2015-09-29 22:01:09 +08:00
except:
pass
# Update credentials DB
try:
if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')):
creds = telemetry_json['data']['credentials']
for user in creds:
creds_add_username(user)
if creds[user].has_key('password'):
creds_add_password(creds[user]['password'])
except StandardError as ex:
print("Exception caught while updating DB credentials: %s" % str(ex))
2015-09-29 22:01:09 +08:00
return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
class LocalRun(restful.Resource):
def get(self):
2016-08-23 00:40:38 +08:00
req_type = request.args.get('type')
if req_type == "interfaces":
return {"interfaces": local_ips()}
else:
return {"message": "unknown action"}
def post(self):
action_json = json.loads(request.data)
2016-08-23 00:40:38 +08:00
if 'action' in action_json:
if action_json["action"] == "monkey" and action_json.get("island_address") is not None:
return {"res": run_local_monkey(action_json.get("island_address"))}
return {"res": (False, "Unknown action")}
2015-10-14 22:20:01 +08:00
class NewConfig(restful.Resource):
2015-09-29 22:01:09 +08:00
def get(self):
2015-12-02 17:18:27 +08:00
config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
2016-08-23 00:40:38 +08:00
if 'name' in config:
2015-09-29 22:01:09 +08:00
del config['name']
return config
def post(self):
config_json = json.loads(request.data)
2015-12-02 17:18:27 +08:00
return mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
2015-09-29 22:01:09 +08:00
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 host_os:
result = get_monkey_executable(host_os.get('type'), host_os.get('machine'))
2015-09-29 22:01:09 +08:00
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 {}
2015-09-29 22:01:09 +08:00
class Root(restful.Resource):
2016-07-24 21:27:56 +08:00
def get(self, action=None):
if not action:
action = request.args.get('action')
if not action:
return {
'status': 'OK',
'mongo': str(mongo.db),
}
2016-08-23 00:40:38 +08:00
elif action == "reset":
2016-07-24 21:27:56 +08:00
mongo.db.config.drop()
mongo.db.monkey.drop()
mongo.db.telemetry.drop()
mongo.db.usernames.drop()
mongo.db.passwords.drop()
init_db()
2016-07-24 21:27:56 +08:00
return {
'status': 'OK',
}
2016-08-23 00:40:38 +08:00
elif action == "killall":
mongo.db.monkey.update({}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False,
multi=True)
2016-07-27 03:32:46 +08:00
return {
'status': 'OK',
}
2016-07-24 21:27:56 +08:00
else:
return {'status': 'BAD',
'reason': 'unknown action'}
2015-09-29 22:01:09 +08:00
2015-09-29 22:01:09 +08:00
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():
2015-09-29 22:01:09 +08:00
if type(value) is bson.objectid.ObjectId:
obj[key] = str(value)
if type(value) is datetime:
obj[key] = str(value)
2015-09-29 22:01:09 +08:00
if type(value) is dict:
obj[key] = normalize_obj(value)
if type(value) is list:
for i in range(0, len(value)):
2015-09-29 22:01:09 +08:00
if type(value[i]) is dict:
value[i] = normalize_obj(value[i])
return obj
2015-09-29 22:01:09 +08:00
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
def update_dead_monkeys():
# Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes
if mongo.db.monkey.find_one({'dead': {'$ne': True}, 'keepalive': {'$gte': datetime.now() - timedelta(minutes=10)}}):
return
mongo.db.monkey.update(
{'keepalive': {'$lte': datetime.now() - timedelta(minutes=10)}, 'dead': {'$ne': True}},
{'$set': {'dead': True, 'modifytime': datetime.now()}}, upsert=False, multi=True)
def get_monkey_executable(host_os, machine):
for download in MONKEY_DOWNLOADS:
if host_os == download.get('type') and machine == download.get('machine'):
return download
return None
def run_local_monkey(island_address):
import platform
import subprocess
import stat
# get the monkey executable suitable to run on the server
result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
if not result:
return (False, "OS Type not found")
2016-08-23 00:40:38 +08:00
monkey_path = os.path.join('binaries', result['filename'])
target_path = os.path.join(os.getcwd(), result['filename'])
# copy the executable to temp path (don't run the monkey from its current location as it may delete itself)
try:
copyfile(monkey_path, target_path)
os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG)
except Exception, exc:
return (False, "Copy file failed: %s" % exc)
# run the monkey
try:
args = ["%s m0nk3y -s %s:%s" % (target_path, island_address, ISLAND_PORT)]
if sys.platform == "win32":
args = "".join(args)
pid = subprocess.Popen(args, shell=True).pid
except Exception, exc:
return (False, "popen failed: %s" % exc)
return (True, "pis: %s" % pid)
def creds_add_username(username):
mongo.db.usernames.update(
{'username': username},
{'$inc': {'count': 1}},
upsert=True
)
def creds_add_password(password):
mongo.db.passwords.update(
{'password': password},
{'$inc': {'count': 1}},
upsert=True
)
### Local ips function
if sys.platform == "win32":
def local_ips():
local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2]
else:
import fcntl
def local_ips():
result = []
try:
is_64bits = sys.maxsize > 2 ** 32
struct_size = 40 if is_64bits else 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
max_possible = 8 # initial value
while True:
2016-08-23 00:40:38 +08:00
struct_bytes = max_possible * struct_size
names = array.array('B', '\0' * struct_bytes)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
2016-08-23 00:40:38 +08:00
struct.pack('iL', struct_bytes, names.buffer_info()[0])
))[0]
2016-08-23 00:40:38 +08:00
if outbytes == struct_bytes:
max_possible *= 2
else:
break
namestr = names.tostring()
for i in range(0, outbytes, struct_size):
addr = socket.inet_ntoa(namestr[i + 20:i + 24])
if not addr.startswith('127'):
result.append(addr)
# name of interface is (namestr[i:i+16].split('\0', 1)[0]
finally:
return result
2016-08-23 00:40:38 +08:00
### End of local ips function
2015-09-29 22:01:09 +08:00
@app.route('/admin/<path:path>')
def send_admin(path):
return send_from_directory('admin/ui', path)
@app.route("/")
def send_to_default():
return redirect('/admin/index.html')
def init_db():
if not "usernames" in mongo.db.collection_names():
mongo.db.usernames.create_index([( "username", 1 )], unique= True)
for username in INITIAL_USERNAMES:
creds_add_username(username)
if not "passwords" in mongo.db.collection_names():
mongo.db.passwords.create_index([( "password", 1 )], unique= True)
for password in INITIAL_PASSWORDS:
creds_add_password(password)
2015-09-29 22:01:09 +08:00
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/<string:guid>')
api.add_resource(LocalRun, '/api/island', '/api/island/')
2015-09-29 22:01:09 +08:00
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>')
2015-10-14 22:20:01 +08:00
api.add_resource(NewConfig, '/api/config/new')
2015-09-29 22:01:09 +08:00
api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', '/api/monkey/download/<string:path>')
if __name__ == '__main__':
2016-08-01 01:40:05 +08:00
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
with app.app_context():
init_db()
2016-08-01 01:40:05 +08:00
http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'})
http_server.listen(ISLAND_PORT)
2016-08-01 01:40:05 +08:00
IOLoop.instance().start()
2016-08-23 00:40:38 +08:00
# app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))