Added POST /api/modules/<path:module_name> to task a module with specified options
Fix multi-stager generation bug More exception handling in empire.py1.6
parent
31eb9d387a
commit
b43da089ef
149
empire
149
empire
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
import sqlite3, argparse, sys, argparse, logging, json, string, os, re, time, signal
|
import sqlite3, argparse, sys, argparse, logging, json, string, os, re, time, signal, copy
|
||||||
from flask import Flask, request, jsonify, make_response, abort
|
from flask import Flask, request, jsonify, make_response, abort
|
||||||
from time import localtime, strftime
|
from time import localtime, strftime
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
|
@ -8,6 +8,7 @@ from Crypto.Random import random
|
||||||
|
|
||||||
# Empire imports
|
# Empire imports
|
||||||
from lib.common import empire
|
from lib.common import empire
|
||||||
|
from lib.common import helpers
|
||||||
|
|
||||||
|
|
||||||
#####################################################
|
#####################################################
|
||||||
|
@ -277,7 +278,7 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password
|
||||||
return jsonify({'error': 'invalid listener ID or name'})
|
return jsonify({'error': 'invalid listener ID or name'})
|
||||||
|
|
||||||
stager = main.stagers.stagers[stagerName]
|
stager = main.stagers.stagers[stagerName]
|
||||||
|
|
||||||
# set all passed options
|
# set all passed options
|
||||||
for option,values in request.json.iteritems():
|
for option,values in request.json.iteritems():
|
||||||
if option != 'StagerName':
|
if option != 'StagerName':
|
||||||
|
@ -290,7 +291,7 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password
|
||||||
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
|
if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
|
||||||
return jsonify({'error': 'required stager options missing'})
|
return jsonify({'error': 'required stager options missing'})
|
||||||
|
|
||||||
stagerOut = stager.options
|
stagerOut = copy.deepcopy(stager.options)
|
||||||
stagerOut['Output'] = stager.generate()
|
stagerOut['Output'] = stager.generate()
|
||||||
|
|
||||||
return jsonify({stagerName: stagerOut})
|
return jsonify({stagerName: stagerOut})
|
||||||
|
@ -310,6 +311,133 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password
|
||||||
return jsonify({'modules': moduleInfo})
|
return jsonify({'modules': moduleInfo})
|
||||||
|
|
||||||
|
|
||||||
|
@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 jsonify({'error': 'invalid module name'})
|
||||||
|
|
||||||
|
moduleInfo = main.modules.modules[module_name].info
|
||||||
|
moduleInfo['options'] = main.modules.modules[module_name].options
|
||||||
|
|
||||||
|
return jsonify({module_name:moduleInfo})
|
||||||
|
|
||||||
|
|
||||||
|
@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 jsonify({'error': 'invalid module name'})
|
||||||
|
|
||||||
|
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 jsonify({'error': 'invalid module option'})
|
||||||
|
|
||||||
|
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 jsonify({'error': 'required module option missing'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not main.agents.is_agent_present(sessionID):
|
||||||
|
return jsonify({'error': 'invalid agent name'})
|
||||||
|
|
||||||
|
# if we're running this module for all agents, skip this validation
|
||||||
|
if sessionID.lower() != "all" and sessionID.lower() != "autorun":
|
||||||
|
modulePSVersion = int(module.info['MinPSVersion'])
|
||||||
|
agentPSVersion = int(main.agents.get_ps_version(sessionID))
|
||||||
|
# check if the agent/module PowerShell versions are compatible
|
||||||
|
if modulePSVersion > agentPSVersion:
|
||||||
|
return jsonify({'error': "module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion)})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': 'exception: %s' %(e)})
|
||||||
|
|
||||||
|
# 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 jsonify({'error': 'module needs to run in an elevated context'})
|
||||||
|
|
||||||
|
|
||||||
|
# actually execute the module
|
||||||
|
moduleData = module.generate()
|
||||||
|
|
||||||
|
if not moduleData or moduleData == "":
|
||||||
|
return jsonify({'error': 'module produced an empty script'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
moduleData.decode('ascii')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return jsonify({'error': 'module source contains non-ascii characters'})
|
||||||
|
|
||||||
|
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 = moduleName.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 = moduleName.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/listeners', methods=['GET'])
|
@app.route('/api/listeners', methods=['GET'])
|
||||||
def get_listeners():
|
def get_listeners():
|
||||||
"""
|
"""
|
||||||
|
@ -388,8 +516,8 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password
|
||||||
if not valid:
|
if not valid:
|
||||||
return jsonify({'error': 'Error validating listener options'})
|
return jsonify({'error': 'Error validating listener options'})
|
||||||
|
|
||||||
main.listeners.add_listener_from_config()
|
success = main.listeners.add_listener_from_config()
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': success})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/agents', methods=['GET'])
|
@app.route('/api/agents', methods=['GET'])
|
||||||
|
@ -512,6 +640,9 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password
|
||||||
else:
|
else:
|
||||||
agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
|
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 jsonify({'error': 'invalid agent name'})
|
||||||
|
|
||||||
for agentNameID in agentNameIDs:
|
for agentNameID in agentNameIDs:
|
||||||
(agentName, agentsSessionID) = agentNameID
|
(agentName, agentsSessionID) = agentNameID
|
||||||
|
|
||||||
|
@ -765,22 +896,26 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('-l', '--listener', nargs='?', const="list", help='Display listener options. Displays all listeners if nothing is specified.')
|
parser.add_argument('-l', '--listener', nargs='?', const="list", help='Display listener options. Displays all listeners if nothing is specified.')
|
||||||
parser.add_argument('-v', '--version', action='store_true', help='Display current Empire version.')
|
parser.add_argument('-v', '--version', action='store_true', help='Display current Empire version.')
|
||||||
parser.add_argument('--rest', action='store_true', help='Run the Empire RESTful API.')
|
parser.add_argument('--rest', action='store_true', help='Run the Empire RESTful API.')
|
||||||
|
parser.add_argument('--restport', nargs='?', help='Port to run the Empire RESTful API on.')
|
||||||
parser.add_argument('--headless', action='store_true', help='Run Empire and the RESTful API headless without the usual interface.')
|
parser.add_argument('--headless', action='store_true', help='Run Empire and the RESTful API headless without the usual interface.')
|
||||||
parser.add_argument('--username', nargs='?', help='Start the RESTful API with the specified username instead of pulling from empire.db')
|
parser.add_argument('--username', nargs='?', help='Start the RESTful API with the specified username instead of pulling from empire.db')
|
||||||
parser.add_argument('--password', nargs='?', help='Start the RESTful API with the specified password instead of pulling from empire.db')
|
parser.add_argument('--password', nargs='?', help='Start the RESTful API with the specified password instead of pulling from empire.db')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.restport:
|
||||||
|
args.restport = '1337'
|
||||||
|
|
||||||
if args.version:
|
if args.version:
|
||||||
print empire.VERSION
|
print empire.VERSION
|
||||||
|
|
||||||
elif args.rest:
|
elif args.rest:
|
||||||
# start just the RESTful API
|
# start just the RESTful API
|
||||||
start_restful_api(startEmpire=False, suppress=False, username=args.username, password=args.password, port=1337)
|
start_restful_api(startEmpire=False, suppress=False, username=args.username, password=args.password, port=args.restport)
|
||||||
|
|
||||||
elif args.headless:
|
elif args.headless:
|
||||||
# start an Empire instance and RESTful API and suppress output
|
# start an Empire instance and RESTful API and suppress output
|
||||||
start_restful_api(startEmpire=True, suppress=True, username=args.username, password=args.password, port=1337)
|
start_restful_api(startEmpire=True, suppress=True, username=args.username, password=args.password, port=args.restport)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# normal execution
|
# normal execution
|
||||||
|
|
|
@ -255,6 +255,10 @@ class MainMenu(cmd.Cmd):
|
||||||
except NavListeners as e:
|
except NavListeners as e:
|
||||||
self.menu_state = "Listeners"
|
self.menu_state = "Listeners"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print helpers.color("[!] Exception: %s" %(e))
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
# print a nicely formatted help menu
|
# print a nicely formatted help menu
|
||||||
# stolen/adapted from recon-ng
|
# stolen/adapted from recon-ng
|
||||||
|
@ -2295,7 +2299,6 @@ class ModuleMenu(cmd.Cmd):
|
||||||
print helpers.color("[!] Error: module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion))
|
print helpers.color("[!] Error: module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion))
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print "exception: ",e
|
|
||||||
print helpers.color("[!] Invalid module or agent PS version!")
|
print helpers.color("[!] Invalid module or agent PS version!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,7 @@ class Listeners:
|
||||||
self.options['Host']['Value'] = value
|
self.options['Host']['Value'] = value
|
||||||
if self.options['CertPath']['Value'] == "":
|
if self.options['CertPath']['Value'] == "":
|
||||||
print helpers.color("[!] Error: Please specify a SSL cert path first")
|
print helpers.color("[!] Error: Please specify a SSL cert path first")
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
parts = value.split(":")
|
parts = value.split(":")
|
||||||
# check if we have a port to extract
|
# check if we have a port to extract
|
||||||
|
@ -188,7 +189,7 @@ class Listeners:
|
||||||
self.options['Port']['Value'] = parts[0]
|
self.options['Port']['Value'] = parts[0]
|
||||||
else:
|
else:
|
||||||
self.options['Port']['Value'] = "443"
|
self.options['Port']['Value'] = "443"
|
||||||
pass
|
|
||||||
elif value.startswith("http"):
|
elif value.startswith("http"):
|
||||||
self.options['Host']['Value'] = value
|
self.options['Host']['Value'] = value
|
||||||
parts = value.split(":")
|
parts = value.split(":")
|
||||||
|
@ -239,6 +240,7 @@ class Listeners:
|
||||||
print helpers.color("[!] Error: invalid option name")
|
print helpers.color("[!] Error: invalid option name")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_listener_options(self):
|
def get_listener_options(self):
|
||||||
"""
|
"""
|
||||||
Return all currently set listener options.
|
Return all currently set listener options.
|
||||||
|
@ -559,6 +561,7 @@ class Listeners:
|
||||||
cur.close()
|
cur.close()
|
||||||
|
|
||||||
self.listeners[result[0]] = None
|
self.listeners[result[0]] = None
|
||||||
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# start up the server object
|
# start up the server object
|
||||||
|
@ -580,11 +583,19 @@ class Listeners:
|
||||||
cur.close()
|
cur.close()
|
||||||
|
|
||||||
# store off this server in the "[id] : server" object array
|
# store off this server in the "[id] : server" object array
|
||||||
# only if the server starts up correctly
|
# only if the server starts up correctly
|
||||||
self.listeners[result[0]] = server
|
self.listeners[result[0]] = server
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
print helpers.color("[!] Error starting listener on port %s" %(port))
|
||||||
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print helpers.color("[!] Required listener option missing.")
|
print helpers.color("[!] Required listener option missing.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def add_pivot_listener(self, listenerName, sessionID, listenPort):
|
def add_pivot_listener(self, listenerName, sessionID, listenPort):
|
||||||
|
|
Loading…
Reference in New Issue