1262 lines
54 KiB
Python
Executable File
1262 lines
54 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import sqlite3, argparse, sys, argparse, logging, json, string
|
|
import os, re, time, signal, copy, base64
|
|
from flask import Flask, request, jsonify, make_response, abort, url_for
|
|
from time import localtime, strftime
|
|
from OpenSSL import SSL
|
|
from Crypto.Random import random
|
|
|
|
# Empire imports
|
|
from lib.common import empire
|
|
from lib.common import helpers
|
|
|
|
global serverExitCommand
|
|
serverExitCommand = 'restart'
|
|
|
|
#####################################################
|
|
#
|
|
# Database interaction methods for the RESTful API
|
|
#
|
|
#####################################################
|
|
|
|
def database_connect():
|
|
"""
|
|
Connect with the backend ./empire.db sqlite database and return the
|
|
connection object.
|
|
"""
|
|
try:
|
|
# set the database connectiont to autocommit w/ isolation level
|
|
conn = sqlite3.connect('./data/empire.db', check_same_thread=False)
|
|
conn.text_factory = str
|
|
conn.isolation_level = None
|
|
return conn
|
|
|
|
except Exception:
|
|
print helpers.color("[!] Could not connect to database")
|
|
print helpers.color("[!] Please run database_setup.py")
|
|
sys.exit()
|
|
|
|
|
|
def execute_db_query(conn, query, args=None):
|
|
"""
|
|
Execute the supplied query on the provided db conn object
|
|
with optional args for a paramaterized query.
|
|
"""
|
|
cur = conn.cursor()
|
|
if args:
|
|
cur.execute(query, args)
|
|
else:
|
|
cur.execute(query)
|
|
results = cur.fetchall()
|
|
cur.close()
|
|
return results
|
|
|
|
|
|
def refresh_api_token(conn):
|
|
"""
|
|
Generates a randomized RESTful API token and updates the value
|
|
in the config stored in the backend database.
|
|
"""
|
|
|
|
# generate a randomized API token
|
|
apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
|
|
|
|
execute_db_query(conn, "UPDATE config SET api_current_token=?", [apiToken])
|
|
|
|
return apiToken
|
|
|
|
|
|
def get_permanent_token(conn):
|
|
"""
|
|
Returns the permanent API token stored in empire.db.
|
|
|
|
If one doesn't exist, it will generate one and store it before returning.
|
|
"""
|
|
|
|
permanentToken = execute_db_query(conn, "SELECT api_permanent_token FROM config")[0]
|
|
if not permanentToken[0]:
|
|
permanentToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
|
|
execute_db_query(conn, "UPDATE config SET api_permanent_token=?", [permanentToken])
|
|
|
|
return permanentToken[0]
|
|
|
|
|
|
####################################################################
|
|
#
|
|
# The Empire RESTful API.
|
|
#
|
|
# Adapted from http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
|
|
# example code at https://gist.github.com/miguelgrinberg/5614326
|
|
#
|
|
# Verb URI Action
|
|
# ---- --- ------
|
|
# GET http://localhost:1337/api/version return the current Empire version
|
|
#
|
|
# GET http://localhost:1337/api/config return the current default config
|
|
#
|
|
# GET http://localhost:1337/api/stagers return all current stagers
|
|
# GET http://localhost:1337/api/stagers/X return the stager with name X
|
|
# POST http://localhost:1337/api/stagers generate a stager given supplied options (need to implement)
|
|
#
|
|
# GET http://localhost:1337/api/modules return all current modules
|
|
# GET http://localhost:1337/api/modules/<name> return the module with the specified name
|
|
# POST http://localhost:1337/api/modules/<name> execute the given module with the specified options
|
|
# POST http://localhost:1337/api/modules/search searches modulesfor a passed term
|
|
# POST http://localhost:1337/api/modules/search/modulename searches module names for a specific term
|
|
# POST http://localhost:1337/api/modules/search/description searches module descriptions for a specific term
|
|
# POST http://localhost:1337/api/modules/search/description searches module comments for a specific term
|
|
# POST http://localhost:1337/api/modules/search/author searches module authors for a specific term
|
|
#
|
|
# GET http://localhost:1337/api/listeners return all current listeners
|
|
# GET http://localhost:1337/api/listeners/Y return the listener with id Y
|
|
# GET http://localhost:1337/api/listeners/options return all listener options
|
|
# POST http://localhost:1337/api/listeners starts a new listener with the specified options
|
|
# DELETE http://localhost:1337/api/listeners/Y kills listener Y
|
|
#
|
|
# GET http://localhost:1337/api/agents return all current agents
|
|
# GET http://localhost:1337/api/agents/stale return all stale agents
|
|
# DELETE http://localhost:1337/api/agents/stale removes stale agents from the database
|
|
# DELETE http://localhost:1337/api/agents/Y removes agent Y from the database
|
|
# GET http://localhost:1337/api/agents/Y return the agent with name Y
|
|
# GET http://localhost:1337/api/agents/Y/results return tasking results for the agent with name Y
|
|
# DELETE http://localhost:1337/api/agents/Y/results deletes the result buffer for agent Y
|
|
# POST http://localhost:1337/api/agents/Y/shell task agent Y to execute a shell command
|
|
# POST http://localhost:1337/api/agents/Y/rename rename agent Y
|
|
# GET/POST http://localhost:1337/api/agents/Y/clear clears the result buffer for agent Y
|
|
# GET/POST http://localhost:1337/api/agents/Y/kill kill agent Y
|
|
#
|
|
# GET http://localhost:1337/api/reporting return all logged events
|
|
# GET http://localhost:1337/api/reporting/agent/X return all logged events for the given agent name X
|
|
# GET http://localhost:1337/api/reporting/type/Y return all logged events of type Y (checkin, task, result, rename)
|
|
# GET http://localhost:1337/api/reporting/msg/Z return all logged events matching message Z, wildcards accepted
|
|
#
|
|
# GET http://localhost:1337/api/creds return stored credentials
|
|
#
|
|
# GET http://localhost:1337/api/admin/login retrieve the API token given the correct username and password
|
|
# GET http://localhost:1337/api/admin/permanenttoken retrieve the permanent API token, generating/storing one if it doesn't already exist
|
|
# GET http://localhost:1337/api/admin/shutdown shutdown the RESTful API
|
|
# GET http://localhost:1337/api/admin/restart restart the RESTful API
|
|
#
|
|
####################################################################
|
|
|
|
def start_restful_api(startEmpire=False, suppress=False, username=None, password=None, port=1337):
|
|
"""
|
|
Kick off the RESTful API with the given parameters.
|
|
|
|
startEmpire - start a complete Empire instance in the backend as well
|
|
suppress - suppress most console output
|
|
username - optional username to use for the API, otherwise pulls from the empire.db config
|
|
password - optional password to use for the API, otherwise pulls from the empire.db config
|
|
port - port to start the API on, defaults to 1337 ;)
|
|
"""
|
|
|
|
app = Flask(__name__)
|
|
|
|
conn = database_connect()
|
|
|
|
global serverExitCommand
|
|
|
|
# if a username/password were not supplied, use the creds stored in the db
|
|
(dbUsername, dbPassword) = execute_db_query(conn, "SELECT api_username, api_password FROM config")[0]
|
|
|
|
if not username:
|
|
username = dbUsername
|
|
|
|
if not password:
|
|
password = dbPassword
|
|
|
|
|
|
class Namespace:
|
|
"""
|
|
Temporary namespace to create the followin base argument object.
|
|
"""
|
|
def __init__(self, **kwargs):
|
|
self.__dict__.update(kwargs)
|
|
|
|
# instantiate an Empire instance in case we need to interact with stagers or listeners
|
|
args = Namespace(debug=None, listener=None, stager=None, stager_options=None, version=False)
|
|
|
|
print ''
|
|
|
|
if startEmpire:
|
|
# if we want to start a full-running empire instance
|
|
print " * Starting a full Empire instance"
|
|
main = empire.MainMenu(args=args)
|
|
else:
|
|
# if we just want the RESTful API, i.e. no listener/etc. startup
|
|
main = empire.MainMenu(args=args, restAPI=True)
|
|
|
|
|
|
print " * Starting Empire RESTful API on port: %s" %(port)
|
|
|
|
# refresh the token for the RESTful API
|
|
apiToken = refresh_api_token(conn)
|
|
print " * RESTful API token: %s" %(apiToken)
|
|
|
|
permanentApiToken = get_permanent_token(conn)
|
|
tokenAllowed = re.compile("^[0-9a-z]{40}")
|
|
|
|
oldStdout = sys.stdout
|
|
if suppress:
|
|
# suppress the normal Flask output
|
|
log = logging.getLogger('werkzeug')
|
|
log.setLevel(logging.ERROR)
|
|
|
|
# suppress all stdout and don't initiate the main cmdloop
|
|
sys.stdout = open(os.devnull, 'w')
|
|
|
|
|
|
# validate API token before every request except for the login URI
|
|
@app.before_request
|
|
def check_token():
|
|
"""
|
|
Before every request, check if a valid token is passed along with the request.
|
|
"""
|
|
if request.path != '/api/admin/login':
|
|
token = request.args.get('token')
|
|
if (not token) or (not tokenAllowed.match(token)):
|
|
return make_response('', 401)
|
|
if (token != apiToken) and (token != permanentApiToken):
|
|
return make_response('', 401)
|
|
|
|
|
|
@app.errorhandler(Exception)
|
|
def exception_handler(error):
|
|
"""
|
|
Generic exception handler.
|
|
"""
|
|
return repr(error)
|
|
|
|
|
|
@app.errorhandler(404)
|
|
def not_found(error):
|
|
"""
|
|
404/not found handler.
|
|
"""
|
|
return make_response(jsonify({'error': 'Not found'}), 404)
|
|
|
|
|
|
@app.route('/api/version', methods=['GET'])
|
|
def get_version():
|
|
"""
|
|
Returns the current Empire version.
|
|
"""
|
|
return jsonify({'version': empire.VERSION})
|
|
|
|
|
|
@app.route('/api/map', methods=['GET'])
|
|
def list_routes():
|
|
"""
|
|
List all of the current registered routes.
|
|
"""
|
|
import urllib
|
|
output = []
|
|
for rule in app.url_map.iter_rules():
|
|
|
|
options = {}
|
|
for arg in rule.arguments:
|
|
options[arg] = "[{0}]".format(arg)
|
|
|
|
methods = ','.join(rule.methods)
|
|
url = url_for(rule.endpoint, **options)
|
|
line = urllib.unquote("[ { '" + rule.endpoint + "': [ { 'methods': '" + methods + "', 'url': '" + url + "' } ] } ]")
|
|
output.append(line)
|
|
|
|
res = ''
|
|
for line in sorted(output):
|
|
res = res + '\r\n' + line
|
|
return jsonify({'Routes':res})
|
|
|
|
|
|
@app.route('/api/config', methods=['GET'])
|
|
def get_config():
|
|
"""
|
|
Returns JSON of the current Empire config.
|
|
"""
|
|
configRaw = execute_db_query(conn, 'SELECT * FROM config')
|
|
|
|
[staging_key, stage0_uri, stage1_uri, stage2_uri, default_delay, default_jitter, default_profile, default_cert_path, default_port, install_path, server_version, ip_whitelist, ip_blacklist, default_lost_limit, autorun_command, autorun_data, api_username, api_password, current_api_token, permanent_api_token] = configRaw[0]
|
|
config = [{"version":empire.VERSION, "staging_key":staging_key, "stage0_uri":stage0_uri, "stage1_uri":stage1_uri, "stage2_uri":stage2_uri, "default_delay":default_delay, "default_jitter":default_jitter, "default_profile":default_profile, "default_cert_path":default_cert_path, "default_port":default_port, "install_path":install_path, "server_version":server_version, "ip_whitelist":ip_whitelist, "ip_blacklist":ip_blacklist, "default_lost_limit":default_lost_limit, "autorun_command":autorun_command, "autorun_data":autorun_data, "api_username":api_username, "api_password":api_password, "current_api_token":current_api_token, "permanent_api_token":permanent_api_token}]
|
|
return jsonify({'config': config})
|
|
|
|
|
|
@app.route('/api/stagers', methods=['GET'])
|
|
def get_stagers():
|
|
"""
|
|
Returns JSON describing all stagers.
|
|
"""
|
|
|
|
stagers = []
|
|
for stagerName, stager in main.stagers.stagers.iteritems():
|
|
info = copy.deepcopy(stager.info)
|
|
info['options'] = stager.options
|
|
info['Name'] = stagerName
|
|
stagers.append(info)
|
|
|
|
return jsonify({'stagers': stagers})
|
|
|
|
|
|
@app.route('/api/stagers/<string:stager_name>', methods=['GET'])
|
|
def get_stagers_name(stager_name):
|
|
"""
|
|
Returns JSON describing the specified stager_name passed.
|
|
"""
|
|
|
|
if stager_name not in main.stagers.stagers:
|
|
return make_response(jsonify({'error': 'stager name %s not found' %(stager_name)}), 404)
|
|
|
|
stagers = []
|
|
for stagerName, stager in main.stagers.stagers.iteritems():
|
|
if stagerName == stager_name:
|
|
info = copy.deepcopy(stager.info)
|
|
info['options'] = stager.options
|
|
info['Name'] = stagerName
|
|
stagers.append(info)
|
|
|
|
return jsonify({'stagers': stagers})
|
|
|
|
|
|
@app.route('/api/stagers', methods=['POST'])
|
|
def generate_stager():
|
|
"""
|
|
Generates a stager with the supplied config and returns JSON information
|
|
describing the generated stager, with 'Output' being the stager output.
|
|
|
|
Required JSON args:
|
|
StagerName - the stager name to generate
|
|
Listener - the Listener name to use for the stager
|
|
"""
|
|
if not request.json or not 'StagerName' in request.json or not 'Listener' in request.json:
|
|
abort(400)
|
|
|
|
stagerName = request.json['StagerName']
|
|
listener = request.json['Listener']
|
|
|
|
if stagerName not in main.stagers.stagers:
|
|
return make_response(jsonify({'error': 'stager name %s not found' %(stagerName)}), 404)
|
|
|
|
if not main.listeners.is_listener_valid(listener):
|
|
return make_response(jsonify({'error': 'invalid listener ID or name'}), 400)
|
|
|
|
stager = main.stagers.stagers[stagerName]
|
|
|
|
# set all passed options
|
|
for option, values in request.json.iteritems():
|
|
if option != 'StagerName':
|
|
if option not in stager.options:
|
|
return make_response(jsonify({'error': 'Invalid option %s, check capitalization.' %(option)}), 400)
|
|
stager.options[option]['Value'] = values
|
|
|
|
# validate stager options
|
|
for option, values in stager.options.iteritems():
|
|
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
|
|
return make_response(jsonify({'error': 'required stager options missing'}), 400)
|
|
|
|
stagerOut = copy.deepcopy(stager.options)
|
|
|
|
if ('OutFile' in stagerOut) and (stagerOut['OutFile']['Value'] != ''):
|
|
# if the output was intended for a file, return the base64 encoded text
|
|
stagerOut['Output'] = base64.b64encode(stager.generate())
|
|
else:
|
|
# otherwise return the text of the stager generation
|
|
stagerOut['Output'] = stager.generate()
|
|
|
|
return jsonify({stagerName: stagerOut})
|
|
|
|
|
|
@app.route('/api/modules', methods=['GET'])
|
|
def get_modules():
|
|
"""
|
|
Returns JSON describing all currently loaded modules.
|
|
"""
|
|
|
|
modules = []
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
moduleInfo = copy.deepcopy(module.info)
|
|
moduleInfo['options'] = module.options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/<path:module_name>', methods=['GET'])
|
|
def get_module_name(module_name):
|
|
"""
|
|
Returns JSON describing the specified currently module.
|
|
"""
|
|
|
|
if module_name not in main.modules.modules:
|
|
return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
|
|
|
|
modules = []
|
|
moduleInfo = copy.deepcopy(main.modules.modules[module_name].info)
|
|
moduleInfo['options'] = main.modules.modules[module_name].options
|
|
moduleInfo['Name'] = module_name
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/<path:module_name>', methods=['POST'])
|
|
def execute_module(module_name):
|
|
"""
|
|
Executes a given module name with the specified parameters.
|
|
"""
|
|
|
|
# ensure the 'Agent' argument is set
|
|
if not request.json or not 'Agent' in request.json:
|
|
abort(400)
|
|
|
|
if module_name not in main.modules.modules:
|
|
return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
|
|
|
|
module = main.modules.modules[module_name]
|
|
|
|
# set all passed module options
|
|
for key, value in request.json.iteritems():
|
|
if key not in module.options:
|
|
return make_response(jsonify({'error': 'invalid module option'}), 400)
|
|
|
|
module.options[key]['Value'] = value
|
|
|
|
# validate module options
|
|
sessionID = module.options['Agent']['Value']
|
|
|
|
for option, values in module.options.iteritems():
|
|
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
|
|
return make_response(jsonify({'error': 'required module option missing'}), 400)
|
|
|
|
try:
|
|
# if we're running this module for all agents, skip this validation
|
|
if sessionID.lower() != "all" and sessionID.lower() != "autorun":
|
|
|
|
if not main.agents.is_agent_present(sessionID):
|
|
return make_response(jsonify({'error': 'invalid agent name'}), 400)
|
|
|
|
modulePSVersion = int(module.info['MinPSVersion'])
|
|
agentPSVersion = int(main.agents.get_language_version(sessionID))
|
|
# check if the agent/module PowerShell versions are compatible
|
|
if modulePSVersion > agentPSVersion:
|
|
return make_response(jsonify({'error': "module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion)}), 400)
|
|
|
|
except Exception as e:
|
|
return make_response(jsonify({'error': 'exception: %s' %(e)}), 400)
|
|
|
|
# check if the module needs admin privs
|
|
if module.info['NeedsAdmin']:
|
|
# if we're running this module for all agents, skip this validation
|
|
if sessionID.lower() != "all" and sessionID.lower() != "autorun":
|
|
if not main.agents.is_agent_elevated(sessionID):
|
|
return make_response(jsonify({'error': 'module needs to run in an elevated context'}), 400)
|
|
|
|
|
|
# actually execute the module
|
|
moduleData = module.generate()
|
|
|
|
if not moduleData or moduleData == "":
|
|
return make_response(jsonify({'error': 'module produced an empty script'}), 400)
|
|
|
|
try:
|
|
moduleData.decode('ascii')
|
|
except UnicodeDecodeError:
|
|
return make_response(jsonify({'error': 'module source contains non-ascii characters'}), 400)
|
|
|
|
moduleData = helpers.strip_powershell_comments(moduleData)
|
|
taskCommand = ""
|
|
|
|
# build the appropriate task command and module data blob
|
|
if str(module.info['Background']).lower() == "true":
|
|
# if this module should be run in the background
|
|
extention = module.info['OutputExtension']
|
|
if extention and extention != "":
|
|
# if this module needs to save its file output to the server
|
|
# format- [15 chars of prefix][5 chars extension][data]
|
|
saveFilePrefix = module_name.split("/")[-1]
|
|
moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData
|
|
taskCommand = "TASK_CMD_JOB_SAVE"
|
|
else:
|
|
taskCommand = "TASK_CMD_JOB"
|
|
|
|
else:
|
|
# if this module is run in the foreground
|
|
extention = module.info['OutputExtension']
|
|
if module.info['OutputExtension'] and module.info['OutputExtension'] != "":
|
|
# if this module needs to save its file output to the server
|
|
# format- [15 chars of prefix][5 chars extension][data]
|
|
saveFilePrefix = module_name.split("/")[-1][:15]
|
|
moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData
|
|
taskCommand = "TASK_CMD_WAIT_SAVE"
|
|
else:
|
|
taskCommand = "TASK_CMD_WAIT"
|
|
|
|
if sessionID.lower() == "all":
|
|
|
|
for agent in main.agents.get_agents():
|
|
sessionID = agent[1]
|
|
main.agents.add_agent_task(sessionID, taskCommand, moduleData)
|
|
msg = "tasked agent %s to run module %s" %(sessionID, module_name)
|
|
main.agents.save_agent_log(sessionID, msg)
|
|
|
|
msg = "tasked all agents to run module %s" %(module_name)
|
|
return jsonify({'success': True, 'msg':msg})
|
|
|
|
else:
|
|
# set the agent's tasking in the cache
|
|
main.agents.add_agent_task(sessionID, taskCommand, moduleData)
|
|
|
|
# update the agent log
|
|
msg = "tasked agent %s to run module %s" %(sessionID, module_name)
|
|
main.agents.save_agent_log(sessionID, msg)
|
|
return jsonify({'success': True, 'msg':msg})
|
|
|
|
|
|
@app.route('/api/modules/search', methods=['POST'])
|
|
def search_modules():
|
|
"""
|
|
Returns JSON describing the the modules matching the passed
|
|
'term' search parameter. Module name, description, comments,
|
|
and author fields are searched.
|
|
"""
|
|
|
|
if not request.json or not 'term':
|
|
abort(400)
|
|
|
|
searchTerm = request.json['term']
|
|
|
|
modules = []
|
|
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
if (searchTerm.lower() == '') or (searchTerm.lower() in moduleName.lower()) or (searchTerm.lower() in ("".join(module.info['Description'])).lower()) or (searchTerm.lower() in ("".join(module.info['Comments'])).lower()) or (searchTerm.lower() in ("".join(module.info['Author'])).lower()):
|
|
|
|
moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
|
|
moduleInfo['options'] = main.modules.modules[moduleName].options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/search/modulename', methods=['POST'])
|
|
def search_modules_name():
|
|
"""
|
|
Returns JSON describing the the modules matching the passed
|
|
'term' search parameter for the modfule name.
|
|
"""
|
|
|
|
if not request.json or not 'term':
|
|
abort(400)
|
|
|
|
searchTerm = request.json['term']
|
|
|
|
modules = []
|
|
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
if (searchTerm.lower() == '') or (searchTerm.lower() in moduleName.lower()):
|
|
|
|
moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
|
|
moduleInfo['options'] = main.modules.modules[moduleName].options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/search/description', methods=['POST'])
|
|
def search_modules_description():
|
|
"""
|
|
Returns JSON describing the the modules matching the passed
|
|
'term' search parameter for the 'Description' field.
|
|
"""
|
|
|
|
if not request.json or not 'term':
|
|
abort(400)
|
|
|
|
searchTerm = request.json['term']
|
|
|
|
modules = []
|
|
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Description'])).lower()):
|
|
|
|
moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
|
|
moduleInfo['options'] = main.modules.modules[moduleName].options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/search/comments', methods=['POST'])
|
|
def search_modules_comments():
|
|
"""
|
|
Returns JSON describing the the modules matching the passed
|
|
'term' search parameter for the 'Comments' field.
|
|
"""
|
|
|
|
if not request.json or not 'term':
|
|
abort(400)
|
|
|
|
searchTerm = request.json['term']
|
|
|
|
modules = []
|
|
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Comments'])).lower()):
|
|
|
|
moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
|
|
moduleInfo['options'] = main.modules.modules[moduleName].options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/modules/search/author', methods=['POST'])
|
|
def search_modules_author():
|
|
"""
|
|
Returns JSON describing the the modules matching the passed
|
|
'term' search parameter for the 'Author' field.
|
|
"""
|
|
|
|
if not request.json or not 'term':
|
|
abort(400)
|
|
|
|
searchTerm = request.json['term']
|
|
|
|
modules = []
|
|
|
|
for moduleName, module in main.modules.modules.iteritems():
|
|
if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Author'])).lower()):
|
|
|
|
moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
|
|
moduleInfo['options'] = main.modules.modules[moduleName].options
|
|
moduleInfo['Name'] = moduleName
|
|
modules.append(moduleInfo)
|
|
|
|
return jsonify({'modules': modules})
|
|
|
|
|
|
@app.route('/api/listeners', methods=['GET'])
|
|
def get_listeners():
|
|
"""
|
|
Returns JSON describing all currently registered listeners.
|
|
"""
|
|
activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners')
|
|
listeners = []
|
|
|
|
for activeListener in activeListenersRaw:
|
|
[ID, name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_lost_limit] = activeListener
|
|
# activeListeners[name] = {'ID':ID, 'name':name, 'host':host, 'port':port, 'cert_path':cert_path, 'staging_key':staging_key, 'default_delay':default_delay, 'default_jitter':default_jitter, 'default_profile':default_profile, 'kill_date':kill_date, 'working_hours':working_hours, 'listener_type':listener_type, 'redirect_target':redirect_target, 'default_lost_limit':default_lost_limit}
|
|
listeners.append({'ID':ID, 'name':name, 'host':host, 'port':port, 'cert_path':cert_path, 'staging_key':staging_key, 'default_delay':default_delay, 'default_jitter':default_jitter, 'default_profile':default_profile, 'kill_date':kill_date, 'working_hours':working_hours, 'listener_type':listener_type, 'redirect_target':redirect_target, 'default_lost_limit':default_lost_limit})
|
|
|
|
return jsonify({'listeners' : listeners})
|
|
|
|
|
|
@app.route('/api/listeners/<string:listener_name>', methods=['GET'])
|
|
def get_listener_name(listener_name):
|
|
"""
|
|
Returns JSON describing the listener specified by listener_name.
|
|
"""
|
|
activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners')
|
|
listeners = []
|
|
|
|
if listener_name != "" and main.listeners.is_listener_valid(listener_name):
|
|
for activeListener in activeListenersRaw:
|
|
[ID, name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_lost_limit] = activeListener
|
|
if name == listener_name:
|
|
listeners.append({'ID':ID, 'name':name, 'host':host, 'port':port, 'cert_path':cert_path, 'staging_key':staging_key, 'default_delay':default_delay, 'default_jitter':default_jitter, 'default_profile':default_profile, 'kill_date':kill_date, 'working_hours':working_hours, 'listener_type':listener_type, 'redirect_target':redirect_target, 'default_lost_limit':default_lost_limit})
|
|
|
|
return jsonify({'listeners' : listeners})
|
|
else:
|
|
return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
|
|
|
|
|
|
@app.route('/api/listeners/<string:listener_name>', methods=['DELETE'])
|
|
def kill_listener(listener_name):
|
|
"""
|
|
Kills the listener specified by listener_name.
|
|
"""
|
|
|
|
if listener_name.lower() == "all":
|
|
activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners')
|
|
for activeListener in activeListenersRaw:
|
|
[ID, name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_lost_limit] = activeListener
|
|
main.listeners.shutdown_listener(name)
|
|
main.listeners.delete_listener(name)
|
|
|
|
return jsonify({'success': True})
|
|
else:
|
|
if listener_name != "" and main.listeners.is_listener_valid(listener_name):
|
|
main.listeners.shutdown_listener(listener_name)
|
|
main.listeners.delete_listener(listener_name)
|
|
return jsonify({'success': True})
|
|
else:
|
|
return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
|
|
|
|
|
|
@app.route('/api/listeners/options', methods=['GET'])
|
|
def get_listener_options():
|
|
"""
|
|
Returns JSON describing the current listener options.
|
|
"""
|
|
return jsonify({'listeneroptions' : [main.listeners.options]})
|
|
|
|
|
|
@app.route('/api/listeners', methods=['POST'])
|
|
def start_listener():
|
|
"""
|
|
Starts a listener with options supplied in the POST.
|
|
"""
|
|
|
|
# set all passed options
|
|
for option, values in request.json.iteritems():
|
|
returnVal = main.listeners.set_listener_option(option, values)
|
|
if not returnVal:
|
|
return make_response(jsonify({'error': 'error setting listener value %s with option %s' %(option, values)}), 400)
|
|
|
|
valid = main.listeners.validate_listener_options()
|
|
if not valid:
|
|
return make_response(jsonify({'error': 'error validating listener options'}), 400)
|
|
|
|
(success, message) = main.listeners.add_listener_from_config()
|
|
if success:
|
|
return jsonify({'success': success, 'msg' : "listener '%s' successfully started." %(message)})
|
|
else:
|
|
return jsonify({'success': success, 'msg' : message})
|
|
|
|
|
|
@app.route('/api/agents', methods=['GET'])
|
|
def get_agents():
|
|
"""
|
|
Returns JSON describing all currently registered agents.
|
|
"""
|
|
activeAgentsRaw = execute_db_query(conn, 'SELECT * FROM agents')
|
|
agents = []
|
|
|
|
for activeAgent in activeAgentsRaw:
|
|
[ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = activeAgent
|
|
agents.append({"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results})
|
|
|
|
return jsonify({'agents' : agents})
|
|
|
|
|
|
@app.route('/api/agents/stale', methods=['GET'])
|
|
def get_agents_stale():
|
|
"""
|
|
Returns JSON describing all stale agents.
|
|
"""
|
|
|
|
agentsRaw = execute_db_query(conn, 'SELECT * FROM agents')
|
|
staleAgents = []
|
|
|
|
for agent in agentsRaw:
|
|
[ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = agent
|
|
|
|
intervalMax = (delay + delay * jitter)+30
|
|
|
|
# get the agent last check in time
|
|
agentTime = time.mktime(time.strptime(lastseen_time, "%Y-%m-%d %H:%M:%S"))
|
|
|
|
if agentTime < time.mktime(time.localtime()) - intervalMax:
|
|
|
|
staleAgents.append({"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results})
|
|
|
|
return jsonify({'agents' : staleAgents})
|
|
|
|
|
|
@app.route('/api/agents/stale', methods=['DELETE'])
|
|
def remove_stale_agent():
|
|
"""
|
|
Removes stale agents from the controller.
|
|
|
|
WARNING: doesn't kill the agent first! Ensure the agent is dead.
|
|
"""
|
|
agentsRaw = execute_db_query(conn, 'SELECT * FROM agents')
|
|
|
|
for agent in agentsRaw:
|
|
[ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = agent
|
|
|
|
intervalMax = (delay + delay * jitter)+30
|
|
|
|
# get the agent last check in time
|
|
agentTime = time.mktime(time.strptime(lastseen_time, "%Y-%m-%d %H:%M:%S"))
|
|
|
|
if agentTime < time.mktime(time.localtime()) - intervalMax:
|
|
execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [sessionID])
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>', methods=['DELETE'])
|
|
def remove_agent(agent_name):
|
|
"""
|
|
Removes an agent from the controller specified by agent_name.
|
|
|
|
WARNING: doesn't kill the agent first! Ensure the agent is dead.
|
|
"""
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
if not agentNameIDs or len(agentNameIDs) == 0:
|
|
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
|
|
|
for agentNameID in agentNameIDs:
|
|
(agentName, agentSessionID) = agentNameID
|
|
|
|
execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [agentSessionID])
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>', methods=['GET'])
|
|
def get_agents_name(agent_name):
|
|
"""
|
|
Returns JSON describing the agent specified by agent_name.
|
|
"""
|
|
activeAgentsRaw = execute_db_query(conn, 'SELECT * FROM agents WHERE name=? OR session_id=?', [agent_name, agent_name])
|
|
activeAgents = []
|
|
|
|
for activeAgent in activeAgentsRaw:
|
|
[ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = activeAgent
|
|
activeAgents.append({"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results})
|
|
|
|
return jsonify({'agents' : activeAgents})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/results', methods=['GET'])
|
|
def get_agent_results(agent_name):
|
|
"""
|
|
Returns JSON describing the agent's results and removes the result field
|
|
from the backend database.
|
|
"""
|
|
|
|
agentTaskResults = []
|
|
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
for agentNameID in agentNameIDs:
|
|
(agentName, agentSessionID) = agentNameID
|
|
|
|
agentResults = execute_db_query(conn, 'SELECT results FROM agents WHERE session_id=?', [agentSessionID])[0]
|
|
|
|
if agentResults and agentResults[0] and agentResults[0] != '':
|
|
out = json.loads(agentResults[0])
|
|
if out:
|
|
agentResults = "\n".join(out)
|
|
else:
|
|
agentResults = ''
|
|
else:
|
|
agentResults = ''
|
|
|
|
agentTaskResults.append({"agentname":agentName, "results":agentResults})
|
|
|
|
return jsonify({'results': agentTaskResults})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/results', methods=['DELETE'])
|
|
def delete_agent_results(agent_name):
|
|
"""
|
|
Removes the specified agent results field from the backend database.
|
|
"""
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
execute_db_query(conn, "UPDATE agents SET results='' WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
execute_db_query(conn, "UPDATE agents SET results='' WHERE name like ? OR session_id like ?", [agent_name, agent_name])
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/shell', methods=['POST'])
|
|
def task_agent_shell(agent_name):
|
|
"""
|
|
Tasks an the specified agent_name to execute a shell command.
|
|
|
|
Takes {'command':'shell_command'}
|
|
"""
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
if not agentNameIDs or len(agentNameIDs) == 0:
|
|
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
|
|
|
command = request.json['command']
|
|
|
|
for agentNameID in agentNameIDs:
|
|
(agentName, agentSessionID) = agentNameID
|
|
|
|
# get existing agent taskings for each agent
|
|
agentTasks = execute_db_query(conn, 'SELECT taskings FROM agents WHERE session_id like ?', [agentSessionID])[0]
|
|
if agentTasks and agentTasks[0]:
|
|
agentTasks = json.loads(agentTasks[0])
|
|
else:
|
|
agentTasks = []
|
|
|
|
# append our new json-ified task and update the backend
|
|
agentTasks.append(['TASK_SHELL', command])
|
|
|
|
execute_db_query(conn, "UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agentTasks), agentSessionID])
|
|
|
|
timeStamp = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
|
execute_db_query(conn, "INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (agentName, "task", "TASK_SHELL - " + command[0:50], timeStamp))
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/rename', methods=['POST'])
|
|
def task_agent_rename(agent_name):
|
|
"""
|
|
Renames the specified agent.
|
|
|
|
Takes {'newname':'NAME'}
|
|
"""
|
|
|
|
agentNameID = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
if not agentNameID or len(agentNameID) == 0:
|
|
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
|
|
|
(agentName, agentSessionID) = agentNameID[0]
|
|
newName = request.json['newname']
|
|
|
|
try:
|
|
result = main.agents.rename_agent(agentName, newName)
|
|
|
|
if not result:
|
|
return make_response(jsonify({'error': 'error in renaming %s to %s, new name may have already been used' %(agentName, newName)}), 400)
|
|
|
|
return jsonify({'success': True})
|
|
|
|
except Exception:
|
|
return make_response(jsonify({'error': 'error in renaming %s to %s' %(agentName, newName)}), 400)
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/clear', methods=['POST', 'GET'])
|
|
def task_agent_clear(agent_name):
|
|
"""
|
|
Clears the tasking buffer for the specified agent.
|
|
"""
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
if not agentNameIDs or len(agentNameIDs) == 0:
|
|
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
|
|
|
for agentNameID in agentNameIDs:
|
|
(agentName, agentSessionID) = agentNameID
|
|
|
|
execute_db_query(conn, "UPDATE agents SET taskings=? WHERE session_id=?", ['', agentSessionID])
|
|
|
|
timeStamp = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
|
execute_db_query(conn, "INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (agentName, "clear", '', timeStamp))
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/agents/<string:agent_name>/kill', methods=['POST', 'GET'])
|
|
def task_agent_kill(agent_name):
|
|
"""
|
|
Tasks the specified agent to exit.
|
|
"""
|
|
if agent_name.lower() == "all":
|
|
# enumerate all target agent sessionIDs
|
|
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
|
|
else:
|
|
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
|
|
|
if not agentNameIDs or len(agentNameIDs) == 0:
|
|
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
|
|
|
|
for agentNameID in agentNameIDs:
|
|
(agentName, agentSessionID) = agentNameID
|
|
|
|
# get existing agent taskings for each agent
|
|
agentTasks = execute_db_query(conn, 'SELECT taskings FROM agents WHERE session_id like ?', [agentSessionID])[0]
|
|
if agentTasks and agentTasks[0]:
|
|
agentTasks = json.loads(agentTasks[0])
|
|
else:
|
|
agentTasks = []
|
|
|
|
# append our new json-ified task and update the backend
|
|
agentTasks.append(['TASK_EXIT', ''])
|
|
|
|
execute_db_query(conn, "UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agentTasks), agentSessionID])
|
|
|
|
timeStamp = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
|
execute_db_query(conn, "INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (agentName, "task", "TASK_EXIT", timeStamp))
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/creds', methods=['GET'])
|
|
def get_creds():
|
|
"""
|
|
Returns JSON describing the credentials stored in the backend database.
|
|
"""
|
|
credsRaw = execute_db_query(conn, 'SELECT * FROM credentials')
|
|
creds = []
|
|
|
|
for credRaw in credsRaw:
|
|
[ID, credtype, domain, username, password, host, sid, notes] = credRaw
|
|
creds.append({"ID":ID, "credtype":credtype, "domain":domain, "username":username, "password":password, "host":host, "sid":sid, "notes":notes})
|
|
|
|
return jsonify({'creds' : creds})
|
|
|
|
|
|
@app.route('/api/reporting', methods=['GET'])
|
|
def get_reporting():
|
|
"""
|
|
Returns JSON describing the reporting events from the backend database.
|
|
"""
|
|
reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting')
|
|
reportingEvents = []
|
|
|
|
for reportingEvent in reportingRaw:
|
|
[ID, name, eventType, message, timestamp] = reportingEvent
|
|
reportingEvents.append({"ID":ID, "agentname":name, "event_type":eventType, "message":message, "timestamp":timestamp})
|
|
|
|
return jsonify({'reporting' : reportingEvents})
|
|
|
|
|
|
@app.route('/api/reporting/agent/<string:reporting_agent>', methods=['GET'])
|
|
def get_reporting_agent(reporting_agent):
|
|
"""
|
|
Returns JSON describing the reporting events from the backend database for
|
|
the agent specified by reporting_agent.
|
|
"""
|
|
|
|
# first resolve the supplied name to a sessionID
|
|
results = execute_db_query(conn, 'SELECT session_id FROM agents WHERE name=?', [reporting_agent])
|
|
if results:
|
|
sessionID = results[0][0]
|
|
else:
|
|
return jsonify({'reporting' : ''})
|
|
|
|
reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting WHERE name=?', [sessionID])
|
|
reportingEvents = []
|
|
|
|
for reportingEvent in reportingRaw:
|
|
[ID, name, eventType, message, timestamp] = reportingEvent
|
|
reportingEvents.append({"ID":ID, "agentname":name, "event_type":eventType, "message":message, "timestamp":timestamp})
|
|
|
|
return jsonify({'reporting' : reportingEvents})
|
|
|
|
|
|
@app.route('/api/reporting/type/<string:event_type>', methods=['GET'])
|
|
def get_reporting_type(event_type):
|
|
"""
|
|
Returns JSON describing the reporting events from the backend database for
|
|
the event type specified by event_type.
|
|
"""
|
|
reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting WHERE event_type=?', [event_type])
|
|
reportingEvents = []
|
|
|
|
for reportingEvent in reportingRaw:
|
|
[ID, name, eventType, message, timestamp] = reportingEvent
|
|
reportingEvents.append({"ID":ID, "agentname":name, "event_type":eventType, "message":message, "timestamp":timestamp})
|
|
|
|
return jsonify({'reporting' : reportingEvents})
|
|
|
|
|
|
@app.route('/api/reporting/msg/<string:msg>', methods=['GET'])
|
|
def get_reporting_msg(msg):
|
|
"""
|
|
Returns JSON describing the reporting events from the backend database for
|
|
the any messages with *msg* specified by msg.
|
|
"""
|
|
reportingRaw = execute_db_query(conn, "SELECT * FROM reporting WHERE message like ?", ['%'+msg+'%'])
|
|
reportingEvents = []
|
|
|
|
for reportingEvent in reportingRaw:
|
|
[ID, name, eventType, message, timestamp] = reportingEvent
|
|
reportingEvents.append({"ID":ID, "agentname":name, "event_type":eventType, "message":message, "timestamp":timestamp})
|
|
|
|
return jsonify({'reporting' : reportingEvents})
|
|
|
|
|
|
@app.route('/api/admin/login', methods=['POST'])
|
|
def server_login():
|
|
"""
|
|
Takes a supplied username and password and returns the current API token
|
|
if authentication is accepted.
|
|
"""
|
|
|
|
if not request.json or not 'username' in request.json or not 'password' in request.json:
|
|
abort(400)
|
|
|
|
suppliedUsername = request.json['username']
|
|
suppliedPassword = request.json['password']
|
|
|
|
# try to prevent some basic bruting
|
|
time.sleep(2)
|
|
|
|
if suppliedUsername == username and suppliedPassword == password:
|
|
return jsonify({'token': apiToken})
|
|
else:
|
|
return make_response('', 401)
|
|
|
|
|
|
@app.route('/api/admin/permanenttoken', methods=['GET'])
|
|
def get_server_perm_token():
|
|
"""
|
|
Returns the 'permanent' API token for the server.
|
|
"""
|
|
permanentToken = get_permanent_token(conn)
|
|
return jsonify({'token': permanentToken})
|
|
|
|
|
|
@app.route('/api/admin/restart', methods=['GET', 'POST', 'PUT'])
|
|
def signal_server_restart():
|
|
"""
|
|
Signal a restart for the Flask server and any Empire instance.
|
|
"""
|
|
restart_server()
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@app.route('/api/admin/shutdown', methods=['GET', 'POST', 'PUT'])
|
|
def signal_server_shutdown():
|
|
"""
|
|
Signal a restart for the Flask server and any Empire instance.
|
|
"""
|
|
shutdown_server()
|
|
return jsonify({'success': True})
|
|
|
|
|
|
if not os.path.exists('./data/empire.pem'):
|
|
print "[!] Error: cannot find certificate ./data/empire.pem"
|
|
sys.exit()
|
|
|
|
|
|
def shutdown_server():
|
|
"""
|
|
Shut down the Flask server and any Empire instance gracefully.
|
|
"""
|
|
global serverExitCommand
|
|
|
|
if suppress:
|
|
# repair stdout
|
|
sys.stdout.close()
|
|
sys.stdout = oldStdout
|
|
|
|
print "\n * Shutting down Empire RESTful API"
|
|
|
|
if conn:
|
|
conn.close()
|
|
|
|
if startEmpire:
|
|
print " * Shutting down the Empire instance"
|
|
main.shutdown()
|
|
|
|
serverExitCommand = 'shutdown'
|
|
|
|
func = request.environ.get('werkzeug.server.shutdown')
|
|
if func is not None:
|
|
func()
|
|
|
|
|
|
def restart_server():
|
|
"""
|
|
Restart the Flask server and any Empire instance.
|
|
"""
|
|
global serverExitCommand
|
|
|
|
shutdown_server()
|
|
|
|
serverExitCommand = 'restart'
|
|
|
|
|
|
def signal_handler(signal, frame):
|
|
"""
|
|
Overrides the keyboardinterrupt signal handler so we can gracefully shut everything down.
|
|
"""
|
|
|
|
global serverExitCommand
|
|
|
|
with app.test_request_context():
|
|
shutdown_server()
|
|
|
|
serverExitCommand = 'shutdown'
|
|
|
|
# repair the original signal handler
|
|
import signal
|
|
signal.signal(signal.SIGINT, signal.default_int_handler)
|
|
sys.exit()
|
|
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
# wrap the Flask connection in SSL and start it
|
|
context = ('./data/empire.pem', './data/empire.pem')
|
|
app.run(host='0.0.0.0', port=int(port), ssl_context=context, threaded=True)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
generalGroup = parser.add_argument_group('General Options')
|
|
generalGroup.add_argument('--debug', nargs='?', const='1', help='Debug level for output (default of 1, 2 for msg display).')
|
|
generalGroup.add_argument('-v', '--version', action='store_true', help='Display current Empire version.')
|
|
|
|
cliGroup = parser.add_argument_group('CLI Payload Options')
|
|
cliGroup.add_argument('-l', '--listener', nargs='?', const="list", help='Display listener options. Displays all listeners if nothing is specified.')
|
|
cliGroup.add_argument('-s', '--stager', nargs='?', const="list", help='Specify a stager to generate. Lists all stagers if none is specified.')
|
|
cliGroup.add_argument('-o', '--stager-options', nargs='*', help="Supply options to set for a stager in OPTION=VALUE format. Lists options if nothing is specified.")
|
|
|
|
restGroup = parser.add_argument_group('RESTful API Options')
|
|
launchGroup = restGroup.add_mutually_exclusive_group()
|
|
launchGroup.add_argument('--rest', action='store_true', help='Run the Empire RESTful API.')
|
|
launchGroup.add_argument('--headless', action='store_true', help='Run Empire and the RESTful API headless without the usual interface.')
|
|
restGroup.add_argument('--restport', type=int, nargs=1, help='Port to run the Empire RESTful API on.')
|
|
restGroup.add_argument('--username', nargs=1, help='Start the RESTful API with the specified username instead of pulling from empire.db')
|
|
restGroup.add_argument('--password', nargs=1, help='Start the RESTful API with the specified password instead of pulling from empire.db')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.restport:
|
|
args.restport = '1337'
|
|
else:
|
|
args.restport = args.restport[0]
|
|
|
|
if args.version:
|
|
print empire.VERSION
|
|
|
|
elif args.rest:
|
|
# start just the RESTful API
|
|
while serverExitCommand == 'restart':
|
|
try:
|
|
start_restful_api(startEmpire=False, suppress=False, username=args.username, password=args.password, port=args.restport)
|
|
except SystemExit as e:
|
|
pass
|
|
|
|
elif args.headless:
|
|
# start an Empire instance and RESTful API and suppress output
|
|
while serverExitCommand == 'restart':
|
|
try:
|
|
start_restful_api(startEmpire=True, suppress=True, username=args.username, password=args.password, port=args.restport)
|
|
except SystemExit as e:
|
|
pass
|
|
else:
|
|
# normal execution
|
|
main = empire.MainMenu(args=args)
|
|
main.cmdloop()
|
|
|
|
sys.exit()
|