2017-08-16 05:41:22 +00:00
#!/usr/bin/env python
2015-08-05 18:36:39 +00:00
2016-09-23 18:04:35 +00:00
import sqlite3, argparse, sys, argparse, logging, json, string
2017-04-22 05:57:11 +00:00
import os, re, time, signal, copy, base64, pickle
2016-05-18 22:23:02 +00:00
from flask import Flask, request, jsonify, make_response, abort, url_for
2017-09-06 15:14:36 +00:00
from time import localtime, strftime, sleep
2017-10-08 02:39:41 +00:00
import hashlib
2016-03-22 21:06:18 +00:00
from OpenSSL import SSL
from Crypto.Random import random
2017-09-06 15:14:36 +00:00
import ssl
2015-08-05 18:36:39 +00:00
# Empire imports
from lib.common import empire
2016-03-24 20:03:31 +00:00
from lib.common import helpers
2016-03-22 21:06:18 +00:00
2016-03-24 20:50:54 +00:00
global serverExitCommand
serverExitCommand = 'restart'
2016-03-22 21:06:18 +00:00
#####################################################
#
# 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
2016-09-23 18:04:35 +00:00
except Exception:
2016-03-22 21:06:18 +00:00
print helpers.color("[!] Could not connect to database")
2017-09-01 01:16:10 +00:00
print helpers.color("[!] Please run setup_database.py")
2016-03-22 21:06:18 +00:00
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()
2016-09-23 18:04:35 +00:00
if args:
2016-03-22 21:06:18 +00:00
cur.execute(query, args)
else:
cur.execute(query)
results = cur.fetchall()
cur.close()
2016-09-23 18:04:35 +00:00
return results
2016-03-22 21:06:18 +00:00
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))
2016-03-22 23:28:23 +00:00
execute_db_query(conn, "UPDATE config SET api_current_token=?", [apiToken])
2016-03-22 21:06:18 +00:00
return apiToken
2016-03-22 23:28:23 +00:00
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]
2016-03-22 21:06:18 +00:00
####################################################################
#
# The Empire RESTful API.
2016-09-23 18:04:35 +00:00
#
2016-03-22 21:06:18 +00:00
# Adapted from http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
# example code at https://gist.github.com/miguelgrinberg/5614326
#
2016-03-26 03:35:03 +00:00
# Verb URI Action
# ---- --- ------
# GET http://localhost:1337/api/version return the current Empire version
2016-09-23 18:04:35 +00:00
#
2016-03-26 03:35:03 +00:00
# GET http://localhost:1337/api/config return the current default config
2016-03-22 21:06:18 +00:00
#
2016-03-26 03:35:03 +00:00
# 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)
2016-03-22 21:06:18 +00:00
#
2016-03-30 20:43:38 +00:00
# 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
2016-03-22 21:06:18 +00:00
#
2016-03-26 03:35:03 +00:00
# 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
2016-03-22 21:06:18 +00:00
#
2016-03-26 03:35:03 +00:00
# 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
2016-03-22 21:06:18 +00:00
#
2016-03-26 03:35:03 +00:00
# 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
2016-03-22 21:06:18 +00:00
#
2016-03-26 04:00:40 +00:00
# GET http://localhost:1337/api/creds return stored credentials
#
2016-03-26 03:35:03 +00:00
# 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
2016-09-23 18:04:35 +00:00
#
2016-03-22 21:06:18 +00:00
####################################################################
2017-09-06 15:14:36 +00:00
def start_restful_api(empireMenu, suppress=False, username=None, password=None, port=1337):
2016-03-22 23:28:23 +00:00
"""
Kick off the RESTful API with the given parameters.
2016-03-22 21:06:18 +00:00
2017-09-06 15:14:36 +00:00
empireMenu - Main empire menu object
2016-03-22 23:28:23 +00:00
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 ;)
"""
2016-03-22 21:06:18 +00:00
app = Flask(__name__)
2016-03-22 23:28:23 +00:00
conn = database_connect()
2017-09-06 15:14:36 +00:00
main = empireMenu
2016-03-24 20:50:54 +00:00
global serverExitCommand
2016-03-22 23:28:23 +00:00
# if a username/password were not supplied, use the creds stored in the db
2017-04-22 20:02:58 +00:00
2016-03-22 23:28:23 +00:00
(dbUsername, dbPassword) = execute_db_query(conn, "SELECT api_username, api_password FROM config")[0]
if not username:
username = dbUsername
2017-04-22 20:02:58 +00:00
else:
execute_db_query(conn, "UPDATE config SET api_username=?", username)
2017-05-30 15:30:32 +00:00
username = username[0]
2016-03-22 23:28:23 +00:00
if not password:
password = dbPassword
2017-04-22 20:02:58 +00:00
else:
execute_db_query(conn, "UPDATE config SET api_password=?", password)
2017-05-30 15:30:32 +00:00
password = password[0]
2017-06-12 17:04:22 +00:00
2016-09-23 18:04:35 +00:00
print ''
2016-03-22 21:06:18 +00:00
2017-06-12 17:04:22 +00:00
2016-03-22 21:06:18 +00:00
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)
2016-03-22 23:28:23 +00:00
permanentApiToken = get_permanent_token(conn)
2016-03-22 21:06:18 +00:00
tokenAllowed = re.compile("^[0-9a-z]{40}")
oldStdout = sys.stdout
if suppress:
# suppress the normal Flask output
log = logging.getLogger('werkzeug')
2016-09-23 18:04:35 +00:00
log.setLevel(logging.ERROR)
2016-03-22 21:06:18 +00:00
# suppress all stdout and don't initiate the main cmdloop
sys.stdout = open(os.devnull, 'w')
2016-03-22 23:28:23 +00:00
# validate API token before every request except for the login URI
2016-03-22 21:06:18 +00:00
@app.before_request
def check_token():
2016-09-23 18:04:35 +00:00
"""
Before every request, check if a valid token is passed along with the request.
"""
2016-03-23 18:30:54 +00:00
if request.path != '/api/admin/login':
2016-03-22 23:28:23 +00:00
token = request.args.get('token')
if (not token) or (not tokenAllowed.match(token)):
2016-04-04 03:15:57 +00:00
return make_response('', 401)
2016-03-22 23:28:23 +00:00
if (token != apiToken) and (token != permanentApiToken):
2016-04-04 03:15:57 +00:00
return make_response('', 401)
2016-03-22 21:06:18 +00:00
@app.errorhandler(Exception)
def exception_handler(error):
2016-09-23 18:04:35 +00:00
"""
Generic exception handler.
"""
2016-03-22 21:06:18 +00:00
return repr(error)
@app.errorhandler(404)
def not_found(error):
2016-09-23 18:04:35 +00:00
"""
404/not found handler.
"""
return make_response(jsonify({'error': 'Not found'}), 404)
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/version', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_version():
"""
Returns the current Empire version.
"""
return jsonify({'version': empire.VERSION})
2016-09-23 18:04:35 +00:00
2016-05-18 22:23:02 +00:00
@app.route('/api/map', methods=['GET'])
def list_routes():
2016-09-23 18:04:35 +00:00
"""
2017-04-22 19:34:35 +00:00
List all of the current registered API routes.
2016-09-23 18:04:35 +00:00
"""
2016-05-18 22:23:02 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-05-18 22:23:02 +00:00
res = ''
for line in sorted(output):
res = res + '\r\n' + line
return jsonify({'Routes':res})
2016-03-22 21:06:18 +00:00
2016-09-23 18:04:35 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/config', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_config():
"""
Returns JSON of the current Empire config.
"""
2017-04-22 19:34:35 +00:00
configRaw = execute_db_query(conn, 'SELECT staging_key, install_path, ip_whitelist, ip_blacklist, autorun_command, autorun_data, rootuser, api_username, api_password, api_current_token, api_permanent_token FROM config')
[staging_key, install_path, ip_whitelist, ip_blacklist, autorun_command, autorun_data, rootuser, api_username, api_password, api_current_token, api_permanent_token] = configRaw[0]
config = [{"api_password":api_password, "api_username":api_username, "autorun_command":autorun_command, "autorun_data":autorun_data, "current_api_token":api_current_token, "install_path":install_path, "ip_blacklist":ip_blacklist, "ip_whitelist":ip_whitelist, "permanent_api_token":api_permanent_token, "staging_key":staging_key, "version":empire.VERSION}]
2016-03-22 21:06:18 +00:00
return jsonify({'config': config})
2016-03-23 18:30:54 +00:00
@app.route('/api/stagers', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_stagers():
"""
Returns JSON describing all stagers.
"""
2016-03-24 22:21:35 +00:00
stagers = []
2016-09-23 18:04:35 +00:00
for stagerName, stager in main.stagers.stagers.iteritems():
info = copy.deepcopy(stager.info)
2016-03-22 21:06:18 +00:00
info['options'] = stager.options
2016-03-24 22:21:35 +00:00
info['Name'] = stagerName
stagers.append(info)
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'stagers': stagers})
2016-03-22 21:06:18 +00:00
2017-04-22 21:20:10 +00:00
@app.route('/api/stagers/<path:stager_name>', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_stagers_name(stager_name):
"""
Returns JSON describing the specified stager_name passed.
"""
2016-03-24 22:21:35 +00:00
if stager_name not in main.stagers.stagers:
2017-04-22 21:20:10 +00:00
return make_response(jsonify({'error': 'stager name %s not found, make sure to use [os]/[name] format, ie. windows/dll' %(stager_name)}), 404)
2016-03-24 22:21:35 +00:00
stagers = []
2016-09-23 18:04:35 +00:00
for stagerName, stager in main.stagers.stagers.iteritems():
if stagerName == stager_name:
info = copy.deepcopy(stager.info)
2016-03-22 21:06:18 +00:00
info['options'] = stager.options
2016-03-24 22:21:35 +00:00
info['Name'] = stagerName
stagers.append(info)
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'stagers': stagers})
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/stagers', methods=['POST'])
2016-03-22 21:06:18 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
stagerName = request.json['StagerName']
listener = request.json['Listener']
if stagerName not in main.stagers.stagers:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'stager name %s not found' %(stagerName)}), 404)
2016-03-22 21:06:18 +00:00
if not main.listeners.is_listener_valid(listener):
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'invalid listener ID or name'}), 400)
2016-03-22 21:06:18 +00:00
stager = main.stagers.stagers[stagerName]
2016-03-24 20:03:31 +00:00
2016-03-22 21:06:18 +00:00
# set all passed options
2016-09-23 18:04:35 +00:00
for option, values in request.json.iteritems():
2016-03-22 21:06:18 +00:00
if option != 'StagerName':
2016-09-23 18:04:35 +00:00
if option not in stager.options:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'Invalid option %s, check capitalization.' %(option)}), 400)
2016-03-22 21:06:18 +00:00
stager.options[option]['Value'] = values
# validate stager options
2016-09-23 18:04:35 +00:00
for option, values in stager.options.iteritems():
2016-03-22 21:06:18 +00:00
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'required stager options missing'}), 400)
2016-03-22 21:06:18 +00:00
2016-03-24 20:03:31 +00:00
stagerOut = copy.deepcopy(stager.options)
2016-03-25 02:24:01 +00:00
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()
2016-03-22 21:06:18 +00:00
return jsonify({stagerName: stagerOut})
2016-03-23 18:30:54 +00:00
@app.route('/api/modules', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_modules():
"""
Returns JSON describing all currently loaded modules.
"""
2016-03-24 22:21:35 +00:00
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-24 22:21:35 +00:00
moduleInfo = copy.deepcopy(module.info)
moduleInfo['options'] = module.options
moduleInfo['Name'] = moduleName
modules.append(moduleInfo)
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'modules': modules})
2016-03-22 21:06:18 +00:00
2016-03-24 20:03:31 +00:00
@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:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
2016-03-24 20:03:31 +00:00
2016-03-24 22:21:35 +00:00
modules = []
moduleInfo = copy.deepcopy(main.modules.modules[module_name].info)
2016-03-24 20:03:31 +00:00
moduleInfo['options'] = main.modules.modules[module_name].options
2016-03-24 22:21:35 +00:00
moduleInfo['Name'] = module_name
modules.append(moduleInfo)
2016-03-24 20:03:31 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'modules': modules})
2016-03-24 20:03:31 +00:00
@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)
2016-09-23 18:04:35 +00:00
2016-03-24 20:03:31 +00:00
if module_name not in main.modules.modules:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
2016-03-24 20:03:31 +00:00
module = main.modules.modules[module_name]
# set all passed module options
2016-09-23 18:04:35 +00:00
for key, value in request.json.iteritems():
2016-03-24 20:03:31 +00:00
if key not in module.options:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'invalid module option'}), 400)
2016-03-24 20:03:31 +00:00
module.options[key]['Value'] = value
# validate module options
sessionID = module.options['Agent']['Value']
2016-09-23 18:04:35 +00:00
for option, values in module.options.iteritems():
2016-03-24 20:03:31 +00:00
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'required module option missing'}), 400)
2016-03-24 20:03:31 +00:00
try:
# if we're running this module for all agents, skip this validation
2016-03-27 01:13:51 +00:00
if sessionID.lower() != "all" and sessionID.lower() != "autorun":
2016-03-30 20:43:38 +00:00
2016-03-27 01:13:51 +00:00
if not main.agents.is_agent_present(sessionID):
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'invalid agent name'}), 400)
2016-03-27 01:13:51 +00:00
2017-05-03 21:14:46 +00:00
modulePSVersion = int(module.info['MinLanguageVersion'])
2017-05-03 17:51:09 +00:00
agentPSVersion = int(main.agents.get_language_version_db(sessionID))
2016-03-24 20:03:31 +00:00
# check if the agent/module PowerShell versions are compatible
if modulePSVersion > agentPSVersion:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': "module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion)}), 400)
2016-03-24 20:03:31 +00:00
except Exception as e:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'exception: %s' %(e)}), 400)
2016-03-24 20:03:31 +00:00
# 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):
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'module needs to run in an elevated context'}), 400)
2016-03-24 20:03:31 +00:00
# actually execute the module
moduleData = module.generate()
if not moduleData or moduleData == "":
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'module produced an empty script'}), 400)
2016-03-24 20:03:31 +00:00
try:
moduleData.decode('ascii')
except UnicodeDecodeError:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'module source contains non-ascii characters'}), 400)
2016-03-24 20:03:31 +00:00
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]
2016-08-11 21:01:10 +00:00
saveFilePrefix = module_name.split("/")[-1]
2016-03-24 20:03:31 +00:00
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]
2016-08-11 21:01:10 +00:00
saveFilePrefix = module_name.split("/")[-1][:15]
2016-03-24 20:03:31 +00:00
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]
2017-05-21 05:53:29 +00:00
taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData)
2016-03-24 20:03:31 +00:00
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)
2017-05-21 05:53:29 +00:00
return jsonify({'success': True, 'taskID': taskID, 'msg':msg})
2016-03-24 20:03:31 +00:00
else:
# set the agent's tasking in the cache
2017-05-21 05:53:29 +00:00
taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData)
2016-03-24 20:03:31 +00:00
# update the agent log
msg = "tasked agent %s to run module %s" %(sessionID, module_name)
main.agents.save_agent_log(sessionID, msg)
2017-05-21 05:53:29 +00:00
return jsonify({'success': True, 'taskID': taskID, 'msg':msg})
2016-03-24 20:03:31 +00:00
2016-03-30 20:43:38 +00:00
@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)
2016-09-23 18:04:35 +00:00
2016-03-30 20:43:38 +00:00
searchTerm = request.json['term']
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-30 20:43:38 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-03-30 20:43:38 +00:00
searchTerm = request.json['term']
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-30 20:43:38 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-03-30 20:43:38 +00:00
searchTerm = request.json['term']
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-30 20:43:38 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-03-30 20:43:38 +00:00
searchTerm = request.json['term']
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-30 20:43:38 +00:00
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)
2016-09-23 18:04:35 +00:00
2016-03-30 20:43:38 +00:00
searchTerm = request.json['term']
modules = []
2016-09-23 18:04:35 +00:00
for moduleName, module in main.modules.modules.iteritems():
2016-03-30 20:43:38 +00:00
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})
2016-03-23 18:30:54 +00:00
@app.route('/api/listeners', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_listeners():
"""
Returns JSON describing all currently registered listeners.
"""
2017-04-18 21:25:34 +00:00
activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners')
2016-03-24 22:21:35 +00:00
listeners = []
2016-03-22 21:06:18 +00:00
for activeListener in activeListenersRaw:
2017-04-18 21:25:34 +00:00
[ID, name, module, listener_type, listener_category, options] = activeListener
2017-04-22 05:57:11 +00:00
listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) })
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'listeners' : listeners})
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/listeners/<string:listener_name>', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_listener_name(listener_name):
"""
Returns JSON describing the listener specified by listener_name.
"""
2017-04-18 21:25:34 +00:00
activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners WHERE name=?', [listener_name])
2016-03-24 22:21:35 +00:00
listeners = []
2016-03-22 21:06:18 +00:00
2017-04-18 21:25:34 +00:00
#if listener_name != "" and main.listeners.is_listener_valid(listener_name):
for activeListener in activeListenersRaw:
[ID, name, module, listener_type, listener_category, options] = activeListener
if name == listener_name:
2017-04-22 05:57:11 +00:00
listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) })
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'listeners' : listeners})
else:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/listeners/<string:listener_name>', methods=['DELETE'])
2016-03-22 21:06:18 +00:00
def kill_listener(listener_name):
"""
Kills the listener specified by listener_name.
"""
if listener_name.lower() == "all":
2017-04-22 05:57:11 +00:00
activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners')
2016-03-22 21:06:18 +00:00
for activeListener in activeListenersRaw:
2017-04-22 05:57:11 +00:00
[ID, name, module, listener_type, listener_category, options] = activeListener
2017-05-01 04:47:42 +00:00
main.listeners.kill_listener(name)
2016-03-22 21:06:18 +00:00
2016-03-23 00:03:18 +00:00
return jsonify({'success': True})
2016-03-22 21:06:18 +00:00
else:
if listener_name != "" and main.listeners.is_listener_valid(listener_name):
2017-05-01 04:47:42 +00:00
main.listeners.kill_listener(listener_name)
2016-03-23 00:03:18 +00:00
return jsonify({'success': True})
2016-03-22 21:06:18 +00:00
else:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
2016-03-22 21:06:18 +00:00
2017-04-30 04:58:32 +00:00
@app.route('/api/listeners/options/<string:listener_type>', methods=['GET'])
def get_listener_options(listener_type):
2016-03-22 21:06:18 +00:00
"""
2017-04-30 04:58:32 +00:00
Returns JSON describing listener options for the specified listener type.
2016-03-22 21:06:18 +00:00
"""
2017-04-29 02:04:10 +00:00
2017-04-30 04:58:32 +00:00
if listener_type.lower() not in main.listeners.loadedListeners:
return make_response(jsonify({'error':'listener type %s not found' %(listener_type)}), 404)
options = main.listeners.loadedListeners[listener_type].options
2017-04-29 02:04:10 +00:00
return jsonify({'listeneroptions' : options})
2016-03-22 21:06:18 +00:00
2017-04-30 04:58:32 +00:00
@app.route('/api/listeners/<string:listener_type>', methods=['POST'])
def start_listener(listener_type):
2016-03-22 21:06:18 +00:00
"""
Starts a listener with options supplied in the POST.
"""
2017-04-30 04:58:32 +00:00
if listener_type.lower() not in main.listeners.loadedListeners:
return make_response(jsonify({'error':'listener type %s not found' %(listener_type)}), 404)
2016-03-22 21:06:18 +00:00
2017-04-30 04:58:32 +00:00
listenerObject = main.listeners.loadedListeners[listener_type]
2016-03-22 21:06:18 +00:00
# set all passed options
2017-04-30 04:58:32 +00:00
for option, values in request.json.iteritems():
if option == "Name":
listenerName = values
returnVal = main.listeners.set_listener_option(listener_type, option, values)
if not returnVal:
return make_response(jsonify({'error': 'error setting listener value %s with option %s' %(option, values)}), 400)
main.listeners.start_listener(listener_type, listenerObject)
#check to see if the listener was created
listenerID = main.listeners.get_listener_id(listenerName)
if listenerID:
return jsonify({'success': 'listener %s successfully started' % listenerName})
else:
return jsonify({'error': 'failed to start listener %s' % listenerName})
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/agents', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_agents():
"""
Returns JSON describing all currently registered agents.
"""
2017-04-22 14:58:39 +00:00
activeAgentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents')
2016-03-24 22:21:35 +00:00
agents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for activeAgent in activeAgentsRaw:
2017-04-22 14:58:39 +00:00
[ID, session_id, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = activeAgent
agents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "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, "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results})
2016-03-22 21:06:18 +00:00
2016-03-24 22:21:35 +00:00
return jsonify({'agents' : agents})
2016-03-22 21:06:18 +00:00
2016-03-23 18:30:54 +00:00
@app.route('/api/agents/stale', methods=['GET'])
2016-03-22 23:46:35 +00:00
def get_agents_stale():
"""
Returns JSON describing all stale agents.
"""
2017-04-22 14:58:39 +00:00
agentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents')
2016-03-24 22:21:35 +00:00
staleAgents = []
2016-09-23 18:04:35 +00:00
2016-03-22 23:46:35 +00:00
for agent in agentsRaw:
2017-04-22 14:58:39 +00:00
[ID, session_id, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = agent
2016-03-22 23:46:35 +00:00
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:
2017-04-22 14:58:39 +00:00
staleAgents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "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, "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results})
2016-03-22 23:46:35 +00:00
return jsonify({'agents' : staleAgents})
2016-03-26 03:35:03 +00:00
@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')
2016-09-23 18:04:35 +00:00
2016-03-26 03:35:03 +00:00
for agent in agentsRaw:
2017-05-01 04:47:42 +00:00
[ID, sessionID, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = agent
2016-03-26 03:35:03 +00:00
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.
2016-09-23 18:04:35 +00:00
2016-03-26 03:35:03 +00:00
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:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-26 03:35:03 +00:00
for agentNameID in agentNameIDs:
(agentName, agentSessionID) = agentNameID
execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [agentSessionID])
return jsonify({'success': True})
2016-03-23 18:30:54 +00:00
@app.route('/api/agents/<string:agent_name>', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_agents_name(agent_name):
"""
Returns JSON describing the agent specified by agent_name.
"""
2017-04-22 14:58:39 +00:00
activeAgentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents ' +
'WHERE name=? OR session_id=?', [agent_name, agent_name])
2016-03-24 22:21:35 +00:00
activeAgents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for activeAgent in activeAgentsRaw:
2017-04-22 14:58:39 +00:00
[ID, session_id, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = activeAgent
2017-04-22 18:31:53 +00:00
activeAgents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "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, "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results})
2016-03-22 21:06:18 +00:00
return jsonify({'agents' : activeAgents})
2016-03-23 18:30:54 +00:00
@app.route('/api/agents/<string:agent_name>/results', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_agent_results(agent_name):
"""
Returns JSON describing the agent's results and removes the result field
from the backend database.
"""
2016-03-24 22:21:35 +00:00
agentTaskResults = []
2016-03-22 21:06:18 +00:00
2016-03-23 01:23:47 +00:00
if agent_name.lower() == "all":
# enumerate all target agent sessionIDs
2017-04-22 18:31:53 +00:00
agentNameIDs = execute_db_query(conn, "SELECT name, session_id FROM agents WHERE name like '%' OR session_id like '%'")
2016-03-23 01:23:47 +00:00
else:
2017-04-22 18:31:53 +00:00
agentNameIDs = execute_db_query(conn, 'SELECT name, session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
2016-03-23 01:23:47 +00:00
for agentNameID in agentNameIDs:
2017-04-22 18:31:53 +00:00
[agentName, agentSessionID] = agentNameID
2016-03-23 01:23:47 +00:00
2017-09-06 15:14:36 +00:00
agentResults = execute_db_query(conn, "SELECT results FROM agents WHERE session_id=?", [agentSessionID])
2016-03-22 21:06:18 +00:00
2017-09-06 15:14:36 +00:00
if agentResults and agentResults[0] and agentResults[0] != '':
agentTaskResults.append({"AgentName":agentSessionID, "AgentResults":agentResults[0]})
2017-05-31 04:07:20 +00:00
2016-03-23 01:23:47 +00:00
return jsonify({'results': agentTaskResults})
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
@app.route('/api/agents/<string:agent_name>/results', methods=['DELETE'])
def delete_agent_results(agent_name):
2016-03-22 21:06:18 +00:00
"""
2016-03-26 03:35:03 +00:00
Removes the specified agent results field from the backend database.
2016-03-22 21:06:18 +00:00
"""
2016-03-26 03:35:03 +00:00
if agent_name.lower() == "all":
# enumerate all target agent sessionIDs
2017-05-01 04:47:42 +00:00
agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
2016-03-26 03:35:03 +00:00
else:
2017-05-01 04:47:42 +00:00
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
if not agentNameIDs or len(agentNameIDs) == 0:
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
for agentNameID in agentNameIDs:
(agentName, agentSessionID) = agentNameID
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
2017-05-02 03:37:21 +00:00
execute_db_query(conn, 'UPDATE agents SET results=? WHERE session_id=?', ['', agentSessionID])
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
return jsonify({'success': True})
@app.route('/api/agents/<string:agent_name>/upload', methods=['POST'])
def task_agent_upload(agent_name):
"""
Tasks the specified agent to upload a file
2016-03-26 03:35:03 +00:00
"""
2017-05-01 04:47:42 +00:00
2016-03-26 03:35:03 +00:00
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])
2016-03-24 20:03:31 +00:00
2016-03-26 03:35:03 +00:00
if not agentNameIDs or len(agentNameIDs) == 0:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
if not request.json['data']:
return make_response(jsonify({'error':'file data not provided'}), 404)
if not request.json['filename']:
return make_response(jsonify({'error':'file name not provided'}), 404)
fileData = request.json['data']
fileName = request.json['filename']
rawBytes = base64.b64decode(fileData)
if len(rawBytes) > 1048576:
return make_response(jsonify({'error':'file size too large'}), 404)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
for agentNameID in agentNameIDs:
(agentName, agentSessionID) = agentNameID
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
msg = "Tasked agent to upload %s : %s" % (fileName, hashlib.md5(rawBytes).hexdigest())
main.agents.save_agent_log(agentSessionID, msg)
data = fileName + "|" + fileData
2017-05-03 21:46:35 +00:00
main.agents.add_agent_task_db(agentSessionID, 'TASK_UPLOAD', data)
2017-05-01 04:47:42 +00:00
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
# add task command to agent taskings
2017-05-13 14:17:18 +00:00
msg = "tasked agent %s to run command %s" %(agentSessionID, command)
main.agents.save_agent_log(agentSessionID, msg)
2017-05-22 14:07:06 +00:00
taskID = main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command)
2016-03-22 21:06:18 +00:00
2017-05-22 14:07:06 +00:00
return jsonify({'success': True, 'taskID': taskID})
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
@app.route('/api/agents/<string:agent_name>/rename', methods=['POST'])
def task_agent_rename(agent_name):
"""
Renames the specified agent.
2016-03-23 01:23:47 +00:00
2016-03-26 03:35:03 +00:00
Takes {'newname':'NAME'}
"""
2016-03-23 01:23:47 +00:00
2016-03-26 03:35:03 +00:00
agentNameID = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
2016-03-23 01:23:47 +00:00
2016-03-26 03:35:03 +00:00
if not agentNameID or len(agentNameID) == 0:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
(agentName, agentSessionID) = agentNameID[0]
newName = request.json['newname']
2016-09-23 18:04:35 +00:00
2016-03-26 03:35:03 +00:00
try:
result = main.agents.rename_agent(agentName, newName)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
if not result:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'error in renaming %s to %s, new name may have already been used' %(agentName, newName)}), 400)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
return jsonify({'success': True})
2016-09-23 18:04:35 +00:00
except Exception:
2016-03-31 01:58:00 +00:00
return make_response(jsonify({'error': 'error in renaming %s to %s' %(agentName, newName)}), 400)
2016-03-26 03:35:03 +00:00
@app.route('/api/agents/<string:agent_name>/clear', methods=['POST', 'GET'])
def task_agent_clear(agent_name):
2016-03-22 21:06:18 +00:00
"""
2016-03-26 03:35:03 +00:00
Clears the tasking buffer for the specified agent.
2016-03-22 21:06:18 +00:00
"""
if agent_name.lower() == "all":
2016-03-26 03:35:03 +00:00
# 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])
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
if not agentNameIDs or len(agentNameIDs) == 0:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
for agentNameID in agentNameIDs:
(agentName, agentSessionID) = agentNameID
2016-03-22 21:06:18 +00:00
2017-05-01 04:47:42 +00:00
main.agents.clear_agent_tasks_db(agentSessionID)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
return jsonify({'success': True})
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
@app.route('/api/agents/<string:agent_name>/kill', methods=['POST', 'GET'])
def task_agent_kill(agent_name):
2016-03-22 21:06:18 +00:00
"""
2016-03-26 03:35:03 +00:00
Tasks the specified agent to exit.
2016-03-22 21:06:18 +00:00
"""
2016-03-26 03:35:03 +00:00
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])
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
if not agentNameIDs or len(agentNameIDs) == 0:
2016-09-23 18:04:35 +00:00
return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
for agentNameID in agentNameIDs:
(agentName, agentSessionID) = agentNameID
2016-03-22 21:06:18 +00:00
2017-05-03 21:14:46 +00:00
# task the agent to exit
2017-05-03 21:46:35 +00:00
msg = "tasked agent %s to exit" %(agentSessionID)
2017-09-02 14:15:45 +00:00
main.agents.save_agent_log(agentSessionID, msg)
2017-05-03 21:14:46 +00:00
main.agents.add_agent_task_db(agentSessionID, 'TASK_EXIT')
2016-03-22 21:06:18 +00:00
2016-03-26 03:35:03 +00:00
return jsonify({'success': True})
2016-03-22 21:06:18 +00:00
2016-03-26 03:49:16 +00:00
@app.route('/api/creds', methods=['GET'])
def get_creds():
"""
Returns JSON describing the credentials stored in the backend database.
"""
2017-04-22 18:43:48 +00:00
credsRaw = execute_db_query(conn, 'SELECT ID, credtype, domain, username, password, host, os, sid, notes FROM credentials')
2016-03-26 03:49:16 +00:00
creds = []
2016-09-23 18:04:35 +00:00
2016-03-26 03:49:16 +00:00
for credRaw in credsRaw:
2017-04-22 18:43:48 +00:00
[ID, credtype, domain, username, password, host, os, sid, notes] = credRaw
creds.append({"ID":ID, "credtype":credtype, "domain":domain, "username":username, "password":password, "host":host, "os":os, "sid":sid, "notes":notes})
2016-03-26 03:49:16 +00:00
return jsonify({'creds' : creds})
2016-03-23 18:30:54 +00:00
@app.route('/api/reporting', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_reporting():
"""
Returns JSON describing the reporting events from the backend database.
"""
2017-04-22 19:01:48 +00:00
reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting')
2016-03-24 22:21:35 +00:00
reportingEvents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for reportingEvent in reportingRaw:
2017-04-22 19:01:48 +00:00
[ID, name, event_type, message, time_stamp, taskID] = reportingEvent
reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID})
2016-03-22 21:06:18 +00:00
return jsonify({'reporting' : reportingEvents})
2016-03-23 18:30:54 +00:00
@app.route('/api/reporting/agent/<string:reporting_agent>', methods=['GET'])
2016-03-22 21:06:18 +00:00
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])
2016-09-23 18:04:35 +00:00
if results:
2016-03-22 21:06:18 +00:00
sessionID = results[0][0]
else:
return jsonify({'reporting' : ''})
2017-04-22 19:01:48 +00:00
reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE name=?', [sessionID])
2016-03-24 22:21:35 +00:00
reportingEvents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for reportingEvent in reportingRaw:
2017-04-22 19:01:48 +00:00
[ID, name, event_type, message, time_stamp, taskID] = reportingEvent
reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID})
2016-03-22 21:06:18 +00:00
return jsonify({'reporting' : reportingEvents})
2016-03-23 18:30:54 +00:00
@app.route('/api/reporting/type/<string:event_type>', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_reporting_type(event_type):
"""
Returns JSON describing the reporting events from the backend database for
the event type specified by event_type.
"""
2017-04-22 19:01:48 +00:00
reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE event_type=?', [event_type])
2016-03-24 22:21:35 +00:00
reportingEvents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for reportingEvent in reportingRaw:
2017-04-22 19:01:48 +00:00
[ID, name, event_type, message, time_stamp, taskID] = reportingEvent
reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID})
2016-03-22 21:06:18 +00:00
return jsonify({'reporting' : reportingEvents})
2016-03-23 18:30:54 +00:00
@app.route('/api/reporting/msg/<string:msg>', methods=['GET'])
2016-03-22 21:06:18 +00:00
def get_reporting_msg(msg):
"""
Returns JSON describing the reporting events from the backend database for
the any messages with *msg* specified by msg.
"""
2017-04-22 19:01:48 +00:00
reportingRaw = execute_db_query(conn, "SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE message like ?", ['%'+msg+'%'])
2016-03-24 22:21:35 +00:00
reportingEvents = []
2016-09-23 18:04:35 +00:00
2016-03-22 21:06:18 +00:00
for reportingEvent in reportingRaw:
2017-04-22 19:01:48 +00:00
[ID, name, event_type, message, time_stamp, taskID] = reportingEvent
2017-04-22 21:29:23 +00:00
reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID})
2016-03-22 21:06:18 +00:00
return jsonify({'reporting' : reportingEvents})
2016-03-23 18:30:54 +00:00
@app.route('/api/admin/login', methods=['POST'])
2016-03-22 23:28:23 +00:00
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)
2016-09-23 18:04:35 +00:00
2017-05-30 15:30:32 +00:00
if suppliedUsername == username and suppliedPassword == password:
2016-03-22 23:28:23 +00:00
return jsonify({'token': apiToken})
else:
return make_response('', 401)
2016-03-23 18:30:54 +00:00
@app.route('/api/admin/permanenttoken', methods=['GET'])
2016-03-22 23:28:23 +00:00
def get_server_perm_token():
"""
Returns the 'permanent' API token for the server.
"""
permanentToken = get_permanent_token(conn)
return jsonify({'token': permanentToken})
2016-03-24 20:50:54 +00:00
@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})
2017-09-26 18:48:23 +00:00
if not os.path.exists('./data/empire-chain.pem'):
2016-03-22 21:06:18 +00:00
print "[!] Error: cannot find certificate ./data/empire.pem"
sys.exit()
def shutdown_server():
"""
2016-03-24 20:50:54 +00:00
Shut down the Flask server and any Empire instance gracefully.
2016-03-22 21:06:18 +00:00
"""
2016-03-24 20:50:54 +00:00
global serverExitCommand
2016-03-22 21:06:18 +00:00
if suppress:
# repair stdout
sys.stdout.close()
sys.stdout = oldStdout
2016-03-23 01:23:47 +00:00
print "\n * Shutting down Empire RESTful API"
2016-03-22 21:06:18 +00:00
2016-09-23 18:04:35 +00:00
if conn:
conn.close()
2017-09-06 15:14:36 +00:00
if suppress:
2016-03-23 01:23:47 +00:00
print " * Shutting down the Empire instance"
2016-03-22 21:06:18 +00:00
main.shutdown()
2016-03-24 20:50:54 +00:00
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()
2016-09-23 18:04:35 +00:00
serverExitCommand = 'restart'
2016-03-24 20:50:54 +00:00
2016-03-22 21:06:18 +00:00
def signal_handler(signal, frame):
2016-09-23 18:04:35 +00:00
"""
Overrides the keyboardinterrupt signal handler so we can gracefully shut everything down.
"""
2016-03-24 20:50:54 +00:00
global serverExitCommand
2016-03-22 21:06:18 +00:00
with app.test_request_context():
shutdown_server()
2016-03-24 20:50:54 +00:00
serverExitCommand = 'shutdown'
# repair the original signal handler
import signal
signal.signal(signal.SIGINT, signal.default_int_handler)
sys.exit()
2016-09-23 18:04:35 +00:00
2017-09-06 15:14:36 +00:00
try:
signal.signal(signal.SIGINT, signal_handler)
except ValueError:
pass
2016-03-22 21:06:18 +00:00
# wrap the Flask connection in SSL and start it
2017-09-02 14:52:05 +00:00
certPath = os.path.abspath("./data/")
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.load_cert_chain("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath))
Cast port from string to int when starting REST service
The REST API won’t start due to a bug:
./empire --rest --username "emp" --password "emp"
[*] Loading modules from: /mnt/hgfs/cjones/Empire/lib/modules/
Starting Empire RESTful API on port: 1337
RESTful API token: 2bjmeuwa6pr6yy4x0n88rauyyl1nve7cekdgkefh Traceback
(most recent call last): File "/usr/lib/python2.7/logging/init.py",
line 853, in emit msg = self.format(record) File
"/usr/lib/python2.7/logging/init.py", line 726, in format return
fmt.format(record) File "/usr/lib/python2.7/logging/init.py", line 465,
in format record.message = record.getMessage() File
"/usr/lib/python2.7/logging/init.py", line 329, in getMessage msg = msg
% self.args TypeError: %d format: a number is required, not str Logged
from file _internal.py, line 87
After casting the port from a string to an int, the REST service works.
2016-05-28 12:28:35 +00:00
app.run(host='0.0.0.0', port=int(port), ssl_context=context, threaded=True)
2016-03-22 21:06:18 +00:00
2015-08-05 18:36:39 +00:00
if __name__ == '__main__':
parser = argparse.ArgumentParser()
2016-05-02 22:13:38 +00:00
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.")
2016-09-23 18:04:35 +00:00
2016-05-02 22:13:38 +00:00
restGroup = parser.add_argument_group('RESTful API Options')
launchGroup = restGroup.add_mutually_exclusive_group()
2017-09-06 15:14:36 +00:00
launchGroup.add_argument('--rest', action='store_true', help='Run Empire and the RESTful API.')
2016-05-02 22:13:38 +00:00
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')
2016-02-16 06:52:32 +00:00
2015-08-05 18:36:39 +00:00
args = parser.parse_args()
2016-02-16 06:52:32 +00:00
2016-03-24 20:03:31 +00:00
if not args.restport:
args.restport = '1337'
2016-05-02 22:13:38 +00:00
else:
args.restport = args.restport[0]
2016-03-24 20:03:31 +00:00
2016-02-16 07:27:37 +00:00
if args.version:
2016-02-18 01:06:33 +00:00
print empire.VERSION
2016-03-22 00:20:03 +00:00
2016-03-22 21:06:18 +00:00
elif args.rest:
2017-09-06 15:14:36 +00:00
# start an Empire instance and RESTful API
main = empire.MainMenu(args=args)
def thread_api(empireMenu):
while serverExitCommand == 'restart':
try:
start_restful_api(empireMenu=empireMenu, suppress=False, username=args.username, password=args.password, port=args.restport)
except SystemExit as e:
pass
thread = helpers.KThread(target=thread_api, args=(main,))
thread.daemon = True
thread.start()
sleep(2)
main.cmdloop()
2016-03-22 00:20:03 +00:00
2016-03-22 21:06:18 +00:00
elif args.headless:
# start an Empire instance and RESTful API and suppress output
2017-09-06 15:14:36 +00:00
main = empire.MainMenu(args=args)
2016-03-24 20:50:54 +00:00
while serverExitCommand == 'restart':
try:
2017-09-06 15:14:36 +00:00
start_restful_api(empireMenu=main, suppress=True, username=args.username, password=args.password, port=args.restport)
2016-03-24 20:50:54 +00:00
except SystemExit as e:
pass
2017-09-06 15:14:36 +00:00
2016-03-22 21:06:18 +00:00
else:
# normal execution
main = empire.MainMenu(args=args)
main.cmdloop()
sys.exit()