· 6 years ago · Jan 21, 2020, 01:26 PM
1#!/usr/bin/env python
2
3import sqlite3, argparse, sys, argparse, logging, json, string, subprocess
4import os, re, time, signal, copy, base64, pickle, random
5from flask import Flask, request, jsonify, make_response, abort, url_for
6from time import localtime, strftime, sleep
7import hashlib
8from OpenSSL import SSL
9import ssl
10import ast
11
12# Empire imports
13from lib.common import empire, helpers
14
15# set global utf8 encoding
16reload(sys)
17sys.setdefaultencoding('utf8')
18
19global serverExitCommand
20serverExitCommand = 'restart'
21
22#####################################################
23#
24# Database interaction methods for the RESTful API
25#
26#####################################################
27
28
29def database_check_docker():
30 """
31 Check for docker and setup database if nessary.
32 """
33 if os.path.exists('/.dockerenv'):
34 if not os.path.exists('data/empire.db'):
35 print '[*] Fresh start in docker, running reset.sh for you'
36 subprocess.call(['./setup/reset.sh'])
37
38
39def database_connect():
40 """
41 Connect with the backend ./empire.db sqlite database and return the
42 connection object.
43 """
44 try:
45 # set the database connectiont to autocommit w/ isolation level
46 conn = sqlite3.connect('./data/empire.db', check_same_thread=False)
47 conn.text_factory = str
48 conn.isolation_level = None
49 return conn
50
51 except Exception:
52 print helpers.color("[!] Could not connect to database")
53 print helpers.color("[!] Please run setup_database.py")
54 sys.exit()
55
56
57def execute_db_query(conn, query, args=None):
58 """
59 Execute the supplied query on the provided db conn object
60 with optional args for a paramaterized query.
61 """
62 cur = conn.cursor()
63 if args:
64 cur.execute(query, args)
65 else:
66 cur.execute(query)
67 results = cur.fetchall()
68 cur.close()
69 return results
70
71
72def refresh_api_token(conn):
73 """
74 Generates a randomized RESTful API token and updates the value
75 in the config stored in the backend database.
76 """
77
78 # generate a randomized API token
79 apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
80
81 execute_db_query(conn, "UPDATE config SET api_current_token=?", [apiToken])
82
83 return apiToken
84
85
86def get_permanent_token(conn):
87 """
88 Returns the permanent API token stored in empire.db.
89
90 If one doesn't exist, it will generate one and store it before returning.
91 """
92
93 permanentToken = execute_db_query(conn, "SELECT api_permanent_token FROM config")[0]
94 permanentToken = permanentToken[0]
95 if not permanentToken:
96 permanentToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
97 execute_db_query(conn, "UPDATE config SET api_permanent_token=?", [permanentToken])
98
99 return permanentToken
100
101
102####################################################################
103#
104# The Empire RESTful API.
105#
106# Adapted from http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
107# example code at https://gist.github.com/miguelgrinberg/5614326
108#
109# Verb URI Action
110# ---- --- ------
111# GET http://localhost:1337/api/version return the current Empire version
112#
113# GET http://localhost:1337/api/config return the current default config
114#
115# GET http://localhost:1337/api/stagers return all current stagers
116# GET http://localhost:1337/api/stagers/X return the stager with name X
117# POST http://localhost:1337/api/stagers generate a stager given supplied options (need to implement)
118#
119# GET http://localhost:1337/api/modules return all current modules
120# GET http://localhost:1337/api/modules/<name> return the module with the specified name
121# POST http://localhost:1337/api/modules/<name> execute the given module with the specified options
122# POST http://localhost:1337/api/modules/search searches modulesfor a passed term
123# POST http://localhost:1337/api/modules/search/modulename searches module names for a specific term
124# POST http://localhost:1337/api/modules/search/description searches module descriptions for a specific term
125# POST http://localhost:1337/api/modules/search/description searches module comments for a specific term
126# POST http://localhost:1337/api/modules/search/author searches module authors for a specific term
127#
128# GET http://localhost:1337/api/listeners return all current listeners
129# GET http://localhost:1337/api/listeners/Y return the listener with id Y
130# GET http://localhost:1337/api/listeners/options return all listener options
131# POST http://localhost:1337/api/listeners starts a new listener with the specified options
132# DELETE http://localhost:1337/api/listeners/Y kills listener Y
133#
134# GET http://localhost:1337/api/agents return all current agents
135# GET http://localhost:1337/api/agents/stale return all stale agents
136# DELETE http://localhost:1337/api/agents/stale removes stale agents from the database
137# DELETE http://localhost:1337/api/agents/Y removes agent Y from the database
138# GET http://localhost:1337/api/agents/Y return the agent with name Y
139# GET http://localhost:1337/api/agents/Y/results return tasking results for the agent with name Y
140# DELETE http://localhost:1337/api/agents/Y/results deletes the result buffer for agent Y
141# POST http://localhost:1337/api/agents/Y/shell task agent Y to execute a shell command
142# POST http://localhost:1337/api/agents/Y/rename rename agent Y
143# GET/POST http://localhost:1337/api/agents/Y/clear clears the result buffer for agent Y
144# GET/POST http://localhost:1337/api/agents/Y/kill kill agent Y
145#
146# GET http://localhost:1337/api/reporting return all logged events
147# GET http://localhost:1337/api/reporting/agent/X return all logged events for the given agent name X
148# GET http://localhost:1337/api/reporting/type/Y return all logged events of type Y (checkin, task, result, rename)
149# GET http://localhost:1337/api/reporting/msg/Z return all logged events matching message Z, wildcards accepted
150#
151# GET http://localhost:1337/api/creds return stored credentials
152# POST http://localhost:1337/api/creds add creds to the database
153#
154# GET http://localhost:1337/api/admin/login retrieve the API token given the correct username and password
155# GET http://localhost:1337/api/admin/permanenttoken retrieve the permanent API token, generating/storing one if it doesn't already exist
156# GET http://localhost:1337/api/admin/shutdown shutdown the RESTful API
157# GET http://localhost:1337/api/admin/restart restart the RESTful API
158#
159####################################################################
160
161def start_restful_api(empireMenu, suppress=False, username=None, password=None, port=1337):
162 """
163 Kick off the RESTful API with the given parameters.
164
165 empireMenu - Main empire menu object
166 suppress - suppress most console output
167 username - optional username to use for the API, otherwise pulls from the empire.db config
168 password - optional password to use for the API, otherwise pulls from the empire.db config
169 port - port to start the API on, defaults to 1337 ;)
170 """
171
172 app = Flask(__name__)
173
174 conn = database_connect()
175
176 main = empireMenu
177
178 global serverExitCommand
179
180 # if a username/password were not supplied, use the creds stored in the db
181
182 (dbUsername, dbPassword) = execute_db_query(conn, "SELECT api_username, api_password FROM config")[0]
183
184 if not username:
185 username = dbUsername
186 else:
187 execute_db_query(conn, "UPDATE config SET api_username=?", username)
188 username = username[0]
189
190 if not password:
191 password = dbPassword
192 else:
193 execute_db_query(conn, "UPDATE config SET api_password=?", password)
194 password = password[0]
195
196
197 print ''
198
199
200
201 print " * Starting Empire RESTful API on port: %s" %(port)
202
203 # refresh the token for the RESTful API
204 apiToken = refresh_api_token(conn)
205 print " * RESTful API token: %s" %(apiToken)
206
207 permanentApiToken = get_permanent_token(conn)
208 tokenAllowed = re.compile("^[0-9a-z]{40}")
209
210 oldStdout = sys.stdout
211 if suppress:
212 # suppress the normal Flask output
213 log = logging.getLogger('werkzeug')
214 log.setLevel(logging.ERROR)
215
216 # suppress all stdout and don't initiate the main cmdloop
217 sys.stdout = open(os.devnull, 'w')
218
219 # validate API token before every request except for the login URI
220 @app.before_request
221 def check_token():
222 """
223 Before every request, check if a valid token is passed along with the request.
224 """
225 if request.path != '/api/admin/login':
226 token = request.args.get('token')
227 if (not token) or (not tokenAllowed.match(token)):
228 return make_response('', 401)
229 if (token != apiToken) and (token != permanentApiToken):
230 return make_response('', 401)
231
232 @app.after_request
233 def add_cors(response):
234 response.headers['Access-Control-Allow-Origin'] = '*'
235 return response
236
237
238 @app.errorhandler(Exception)
239 def exception_handler(error):
240 """
241 Generic exception handler.
242 """
243 return repr(error)
244
245
246 @app.errorhandler(404)
247 def not_found(error):
248 """
249 404/not found handler.
250 """
251 return make_response(jsonify({'error': 'Not found'}), 404)
252
253
254 @app.route('/api/version', methods=['GET'])
255 def get_version():
256 """
257 Returns the current Empire version.
258 """
259 return jsonify({'version': empire.VERSION})
260
261
262 @app.route('/api/map', methods=['GET'])
263 def list_routes():
264 """
265 List all of the current registered API routes.
266 """
267 import urllib
268 output = []
269 for rule in app.url_map.iter_rules():
270
271 options = {}
272 for arg in rule.arguments:
273 options[arg] = "[{0}]".format(arg)
274
275 methods = ','.join(rule.methods)
276 url = url_for(rule.endpoint, **options)
277 line = urllib.unquote("[ { '" + rule.endpoint + "': [ { 'methods': '" + methods + "', 'url': '" + url + "' } ] } ]")
278 output.append(line)
279
280 res = ''
281 for line in sorted(output):
282 res = res + '\r\n' + line
283 return jsonify({'Routes':res})
284
285
286 @app.route('/api/config', methods=['GET'])
287 def get_config():
288 """
289 Returns JSON of the current Empire config.
290 """
291 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')
292
293 [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]
294 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}]
295
296 return jsonify({'config': config})
297
298
299 @app.route('/api/stagers', methods=['GET'])
300 def get_stagers():
301 """
302 Returns JSON describing all stagers.
303 """
304
305 stagers = []
306 for stagerName, stager in main.stagers.stagers.iteritems():
307 info = copy.deepcopy(stager.info)
308 info['options'] = stager.options
309 info['Name'] = stagerName
310 stagers.append(info)
311
312 return jsonify({'stagers': stagers})
313
314
315 @app.route('/api/stagers/<path:stager_name>', methods=['GET'])
316 def get_stagers_name(stager_name):
317 """
318 Returns JSON describing the specified stager_name passed.
319 """
320 if stager_name not in main.stagers.stagers:
321 return make_response(jsonify({'error': 'stager name %s not found, make sure to use [os]/[name] format, ie. windows/dll' %(stager_name)}), 404)
322
323 stagers = []
324 for stagerName, stager in main.stagers.stagers.iteritems():
325 if stagerName == stager_name:
326 info = copy.deepcopy(stager.info)
327 info['options'] = stager.options
328 info['Name'] = stagerName
329 stagers.append(info)
330
331 return jsonify({'stagers': stagers})
332
333
334 @app.route('/api/stagers', methods=['POST'])
335 def generate_stager():
336 """
337 Generates a stager with the supplied config and returns JSON information
338 describing the generated stager, with 'Output' being the stager output.
339
340 Required JSON args:
341 StagerName - the stager name to generate
342 Listener - the Listener name to use for the stager
343 """
344 if not request.json or not 'StagerName' in request.json or not 'Listener' in request.json:
345 abort(400)
346
347 stagerName = request.json['StagerName']
348 listener = request.json['Listener']
349
350 if stagerName not in main.stagers.stagers:
351 return make_response(jsonify({'error': 'stager name %s not found' %(stagerName)}), 404)
352
353 if not main.listeners.is_listener_valid(listener):
354 return make_response(jsonify({'error': 'invalid listener ID or name'}), 400)
355
356 stager = main.stagers.stagers[stagerName]
357
358 # set all passed options
359 for option, values in request.json.iteritems():
360 if option != 'StagerName':
361 if option not in stager.options:
362 return make_response(jsonify({'error': 'Invalid option %s, check capitalization.' %(option)}), 400)
363 stager.options[option]['Value'] = values
364
365 # validate stager options
366 for option, values in stager.options.iteritems():
367 if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
368 return make_response(jsonify({'error': 'required stager options missing'}), 400)
369
370 stagerOut = copy.deepcopy(stager.options)
371
372 if ('OutFile' in stagerOut) and (stagerOut['OutFile']['Value'] != ''):
373 # if the output was intended for a file, return the base64 encoded text
374 stagerOut['Output'] = base64.b64encode(stager.generate())
375 else:
376 # otherwise return the text of the stager generation
377 stagerOut['Output'] = stager.generate()
378
379 return jsonify({stagerName: stagerOut})
380
381
382 @app.route('/api/modules', methods=['GET'])
383 def get_modules():
384 """
385 Returns JSON describing all currently loaded modules.
386 """
387
388 modules = []
389 for moduleName, module in main.modules.modules.iteritems():
390 moduleInfo = copy.deepcopy(module.info)
391 moduleInfo['options'] = module.options
392 moduleInfo['Name'] = moduleName
393 modules.append(moduleInfo)
394
395 return jsonify({'modules': modules})
396
397
398 @app.route('/api/modules/<path:module_name>', methods=['GET'])
399 def get_module_name(module_name):
400 """
401 Returns JSON describing the specified currently module.
402 """
403
404 if module_name not in main.modules.modules:
405 return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
406
407 modules = []
408 moduleInfo = copy.deepcopy(main.modules.modules[module_name].info)
409 moduleInfo['options'] = main.modules.modules[module_name].options
410 moduleInfo['Name'] = module_name
411 modules.append(moduleInfo)
412
413 return jsonify({'modules': modules})
414
415
416 @app.route('/api/modules/<path:module_name>', methods=['POST'])
417 def execute_module(module_name):
418 """
419 Executes a given module name with the specified parameters.
420 """
421
422 # ensure the 'Agent' argument is set
423 if not request.json or not 'Agent' in request.json:
424 abort(400)
425
426 if module_name not in main.modules.modules:
427 return make_response(jsonify({'error': 'module name %s not found' %(module_name)}), 404)
428
429 module = main.modules.modules[module_name]
430
431 # set all passed module options
432 for key, value in request.json.iteritems():
433 if key not in module.options:
434 return make_response(jsonify({'error': 'invalid module option'}), 400)
435
436 module.options[key]['Value'] = value
437
438 # validate module options
439 sessionID = module.options['Agent']['Value']
440
441 for option, values in module.options.iteritems():
442 if values['Required'] and ((not values['Value']) or (values['Value'] == '')):
443 return make_response(jsonify({'error': 'required module option missing'}), 400)
444
445 try:
446 # if we're running this module for all agents, skip this validation
447 if sessionID.lower() != "all" and sessionID.lower() != "autorun":
448
449 if not main.agents.is_agent_present(sessionID):
450 return make_response(jsonify({'error': 'invalid agent name'}), 400)
451
452 moduleVersion = float(module.info['MinLanguageVersion'])
453 agentVersion = float(main.agents.get_language_version_db(sessionID))
454 # check if the agent/module PowerShell versions are compatible
455 if moduleVersion > agentVersion:
456 return make_response(jsonify({'error': "module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion)}), 400)
457
458 except Exception as e:
459 return make_response(jsonify({'error': 'exception: %s' %(e)}), 400)
460
461 # check if the module needs admin privs
462 if module.info['NeedsAdmin']:
463 # if we're running this module for all agents, skip this validation
464 if sessionID.lower() != "all" and sessionID.lower() != "autorun":
465 if not main.agents.is_agent_elevated(sessionID):
466 return make_response(jsonify({'error': 'module needs to run in an elevated context'}), 400)
467
468
469 # actually execute the module
470 moduleData = module.generate()
471
472 if not moduleData or moduleData == "":
473 return make_response(jsonify({'error': 'module produced an empty script'}), 400)
474
475 try:
476 moduleData.decode('ascii')
477 except UnicodeDecodeError:
478 return make_response(jsonify({'error': 'module source contains non-ascii characters'}), 400)
479
480 moduleData = helpers.strip_powershell_comments(moduleData)
481 taskCommand = ""
482
483 # build the appropriate task command and module data blob
484 if str(module.info['Background']).lower() == "true":
485 # if this module should be run in the background
486 extention = module.info['OutputExtension']
487 if extention and extention != "":
488 # if this module needs to save its file output to the server
489 # format- [15 chars of prefix][5 chars extension][data]
490 saveFilePrefix = module_name.split("/")[-1]
491 moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData
492 taskCommand = "TASK_CMD_JOB_SAVE"
493 else:
494 taskCommand = "TASK_CMD_JOB"
495
496 else:
497 # if this module is run in the foreground
498 extention = module.info['OutputExtension']
499 if module.info['OutputExtension'] and module.info['OutputExtension'] != "":
500 # if this module needs to save its file output to the server
501 # format- [15 chars of prefix][5 chars extension][data]
502 saveFilePrefix = module_name.split("/")[-1][:15]
503 moduleData = saveFilePrefix.rjust(15) + extention.rjust(5) + moduleData
504 taskCommand = "TASK_CMD_WAIT_SAVE"
505 else:
506 taskCommand = "TASK_CMD_WAIT"
507
508 if sessionID.lower() == "all":
509
510 for agent in main.agents.get_agents():
511 sessionID = agent[1]
512 taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData)
513 msg = "tasked agent %s to run module %s" %(sessionID, module_name)
514 main.agents.save_agent_log(sessionID, msg)
515
516 msg = "tasked all agents to run module %s" %(module_name)
517 return jsonify({'success': True, 'taskID': taskID, 'msg':msg})
518
519 else:
520 # set the agent's tasking in the cache
521 taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData)
522
523 # update the agent log
524 msg = "tasked agent %s to run module %s" %(sessionID, module_name)
525 main.agents.save_agent_log(sessionID, msg)
526 return jsonify({'success': True, 'taskID': taskID, 'msg':msg})
527
528
529 @app.route('/api/modules/search', methods=['POST'])
530 def search_modules():
531 """
532 Returns JSON describing the the modules matching the passed
533 'term' search parameter. Module name, description, comments,
534 and author fields are searched.
535 """
536
537 if not request.json or not 'term':
538 abort(400)
539
540 searchTerm = request.json['term']
541
542 modules = []
543
544 for moduleName, module in main.modules.modules.iteritems():
545 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()):
546
547 moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
548 moduleInfo['options'] = main.modules.modules[moduleName].options
549 moduleInfo['Name'] = moduleName
550 modules.append(moduleInfo)
551
552 return jsonify({'modules': modules})
553
554
555 @app.route('/api/modules/search/modulename', methods=['POST'])
556 def search_modules_name():
557 """
558 Returns JSON describing the the modules matching the passed
559 'term' search parameter for the modfule name.
560 """
561
562 if not request.json or not 'term':
563 abort(400)
564
565 searchTerm = request.json['term']
566
567 modules = []
568
569 for moduleName, module in main.modules.modules.iteritems():
570 if (searchTerm.lower() == '') or (searchTerm.lower() in moduleName.lower()):
571
572 moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
573 moduleInfo['options'] = main.modules.modules[moduleName].options
574 moduleInfo['Name'] = moduleName
575 modules.append(moduleInfo)
576
577 return jsonify({'modules': modules})
578
579
580 @app.route('/api/modules/search/description', methods=['POST'])
581 def search_modules_description():
582 """
583 Returns JSON describing the the modules matching the passed
584 'term' search parameter for the 'Description' field.
585 """
586
587 if not request.json or not 'term':
588 abort(400)
589
590 searchTerm = request.json['term']
591
592 modules = []
593
594 for moduleName, module in main.modules.modules.iteritems():
595 if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Description'])).lower()):
596
597 moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
598 moduleInfo['options'] = main.modules.modules[moduleName].options
599 moduleInfo['Name'] = moduleName
600 modules.append(moduleInfo)
601
602 return jsonify({'modules': modules})
603
604
605 @app.route('/api/modules/search/comments', methods=['POST'])
606 def search_modules_comments():
607 """
608 Returns JSON describing the the modules matching the passed
609 'term' search parameter for the 'Comments' field.
610 """
611
612 if not request.json or not 'term':
613 abort(400)
614
615 searchTerm = request.json['term']
616
617 modules = []
618
619 for moduleName, module in main.modules.modules.iteritems():
620 if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Comments'])).lower()):
621
622 moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
623 moduleInfo['options'] = main.modules.modules[moduleName].options
624 moduleInfo['Name'] = moduleName
625 modules.append(moduleInfo)
626
627 return jsonify({'modules': modules})
628
629
630 @app.route('/api/modules/search/author', methods=['POST'])
631 def search_modules_author():
632 """
633 Returns JSON describing the the modules matching the passed
634 'term' search parameter for the 'Author' field.
635 """
636
637 if not request.json or not 'term':
638 abort(400)
639
640 searchTerm = request.json['term']
641
642 modules = []
643
644 for moduleName, module in main.modules.modules.iteritems():
645 if (searchTerm.lower() == '') or (searchTerm.lower() in ("".join(module.info['Author'])).lower()):
646
647 moduleInfo = copy.deepcopy(main.modules.modules[moduleName].info)
648 moduleInfo['options'] = main.modules.modules[moduleName].options
649 moduleInfo['Name'] = moduleName
650 modules.append(moduleInfo)
651
652 return jsonify({'modules': modules})
653
654
655 @app.route('/api/listeners', methods=['GET'])
656 def get_listeners():
657 """
658 Returns JSON describing all currently registered listeners.
659 """
660 activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners')
661 listeners = []
662
663 for activeListener in activeListenersRaw:
664 [ID, name, module, listener_type, listener_category, options] = activeListener
665 listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) })
666
667
668 return jsonify({'listeners' : listeners})
669
670
671 @app.route('/api/listeners/<string:listener_name>', methods=['GET'])
672 def get_listener_name(listener_name):
673 """
674 Returns JSON describing the listener specified by listener_name.
675 """
676 activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners WHERE name=?', [listener_name])
677 listeners = []
678
679 #if listener_name != "" and main.listeners.is_listener_valid(listener_name):
680 for activeListener in activeListenersRaw:
681 [ID, name, module, listener_type, listener_category, options] = activeListener
682 if name == listener_name:
683 listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) })
684
685 return jsonify({'listeners' : listeners})
686 else:
687 return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
688
689
690 @app.route('/api/listeners/<string:listener_name>', methods=['DELETE'])
691 def kill_listener(listener_name):
692 """
693 Kills the listener specified by listener_name.
694 """
695 if listener_name.lower() == "all":
696 activeListenersRaw = execute_db_query(conn, 'SELECT id, name, module, listener_type, listener_category, options FROM listeners')
697 for activeListener in activeListenersRaw:
698 [ID, name, module, listener_type, listener_category, options] = activeListener
699 main.listeners.kill_listener(name)
700
701 return jsonify({'success': True})
702 else:
703 if listener_name != "" and main.listeners.is_listener_valid(listener_name):
704 main.listeners.kill_listener(listener_name)
705 return jsonify({'success': True})
706 else:
707 return make_response(jsonify({'error': 'listener name %s not found' %(listener_name)}), 404)
708
709
710 @app.route('/api/listeners/options/<string:listener_type>', methods=['GET'])
711 def get_listener_options(listener_type):
712 """
713 Returns JSON describing listener options for the specified listener type.
714 """
715
716 if listener_type.lower() not in main.listeners.loadedListeners:
717 return make_response(jsonify({'error':'listener type %s not found' %(listener_type)}), 404)
718
719 options = main.listeners.loadedListeners[listener_type].options
720 return jsonify({'listeneroptions' : options})
721
722
723 @app.route('/api/listeners/<string:listener_type>', methods=['POST'])
724 def start_listener(listener_type):
725 """
726 Starts a listener with options supplied in the POST.
727 """
728 if listener_type.lower() not in main.listeners.loadedListeners:
729 return make_response(jsonify({'error':'listener type %s not found' %(listener_type)}), 404)
730
731 listenerObject = main.listeners.loadedListeners[listener_type]
732 # set all passed options
733 for option, values in request.json.iteritems():
734 if type(values) == unicode:
735 values = values.encode('utf8')
736 if option == "Name":
737 listenerName = values
738
739 returnVal = main.listeners.set_listener_option(listener_type, option, values)
740 if not returnVal:
741 return make_response(jsonify({'error': 'error setting listener value %s with option %s' %(option, values)}), 400)
742
743 main.listeners.start_listener(listener_type, listenerObject)
744
745 #check to see if the listener was created
746 listenerID = main.listeners.get_listener_id(listenerName)
747 if listenerID:
748 return jsonify({'success': 'listener %s successfully started' % listenerName})
749 else:
750 return jsonify({'error': 'failed to start listener %s' % listenerName})
751
752
753 @app.route('/api/agents', methods=['GET'])
754 def get_agents():
755 """
756 Returns JSON describing all currently registered agents.
757 """
758 activeAgentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
759 'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
760 'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents')
761 agents = []
762
763 for activeAgent in activeAgentsRaw:
764 [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
765
766 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.decode('latin-1').encode("utf-8"), "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})
767
768 return jsonify({'agents' : agents})
769
770
771 @app.route('/api/agents/stale', methods=['GET'])
772 def get_agents_stale():
773 """
774 Returns JSON describing all stale agents.
775 """
776
777 agentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
778 'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
779 'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents')
780 staleAgents = []
781
782 for agent in agentsRaw:
783 [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
784
785 intervalMax = (delay + delay * jitter)+30
786
787 # get the agent last check in time
788 agentTime = time.mktime(time.strptime(lastseen_time, "%Y-%m-%d %H:%M:%S"))
789
790 if agentTime < time.mktime(time.localtime()) - intervalMax:
791
792 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})
793
794 return jsonify({'agents' : staleAgents})
795
796
797 @app.route('/api/agents/stale', methods=['DELETE'])
798 def remove_stale_agent():
799 """
800 Removes stale agents from the controller.
801
802 WARNING: doesn't kill the agent first! Ensure the agent is dead.
803 """
804 agentsRaw = execute_db_query(conn, 'SELECT * FROM agents')
805
806 for agent in agentsRaw:
807 [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
808
809 intervalMax = (delay + delay * jitter)+30
810
811 # get the agent last check in time
812 agentTime = time.mktime(time.strptime(lastseen_time, "%Y-%m-%d %H:%M:%S"))
813
814 if agentTime < time.mktime(time.localtime()) - intervalMax:
815 execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [sessionID])
816
817 return jsonify({'success': True})
818
819
820 @app.route('/api/agents/<string:agent_name>', methods=['DELETE'])
821 def remove_agent(agent_name):
822 """
823 Removes an agent from the controller specified by agent_name.
824
825 WARNING: doesn't kill the agent first! Ensure the agent is dead.
826 """
827 if agent_name.lower() == "all":
828 # enumerate all target agent sessionIDs
829 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
830 else:
831 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
832
833 if not agentNameIDs or len(agentNameIDs) == 0:
834 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
835
836 for agentNameID in agentNameIDs:
837 (agentName, agentSessionID) = agentNameID
838
839 execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [agentSessionID])
840
841 return jsonify({'success': True})
842
843
844 @app.route('/api/agents/<string:agent_name>', methods=['GET'])
845 def get_agents_name(agent_name):
846 """
847 Returns JSON describing the agent specified by agent_name.
848 """
849 activeAgentsRaw = execute_db_query(conn, 'SELECT id, session_id, listener, name, language, language_version, delay, jitter, external_ip, '+
850 'internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, '+
851 'lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results FROM agents ' +
852 'WHERE name=? OR session_id=?', [agent_name, agent_name])
853 activeAgents = []
854
855 for activeAgent in activeAgentsRaw:
856 [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
857 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})
858
859 return jsonify({'agents' : activeAgents})
860
861
862 @app.route('/api/agents/<string:agent_name>/results', methods=['GET'])
863 def get_agent_results(agent_name):
864 """
865 Returns JSON describing the agent's results and removes the result field
866 from the backend database.
867 """
868 agentTaskResults = []
869
870 if agent_name.lower() == "all":
871 # enumerate all target agent sessionIDs
872 agentNameIDs = execute_db_query(conn, "SELECT name, session_id FROM agents WHERE name like '%' OR session_id like '%'")
873 else:
874 agentNameIDs = execute_db_query(conn, 'SELECT name, session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
875
876 for agentNameID in agentNameIDs:
877 [agentName, agentSessionID] = agentNameID
878
879 agentResults = execute_db_query(conn, "SELECT results.id,taskings.data AS command,results.data AS response FROM results INNER JOIN taskings ON results.id = taskings.id AND results.agent = taskings.agent where results.agent=?;", [agentSessionID])
880
881 results = []
882 if len(agentResults) > 0:
883 for taskID, command, result in agentResults:
884 results.append({'taskID': taskID, 'command': command, 'results': result})
885
886 agentTaskResults.append({"AgentName": agentSessionID, "AgentResults": results})
887 else:
888 agentTaskResults.append({"AgentName": agentSessionID, "AgentResults": []})
889
890 return jsonify({'results': agentTaskResults})
891
892
893 @app.route('/api/agents/<string:agent_name>/results', methods=['DELETE'])
894 def delete_agent_results(agent_name):
895 """
896 Removes the specified agent results field from the backend database.
897 """
898 if agent_name.lower() == "all":
899 # enumerate all target agent sessionIDs
900 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
901 else:
902 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
903
904 if not agentNameIDs or len(agentNameIDs) == 0:
905 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
906
907 for agentNameID in agentNameIDs:
908 (agentName, agentSessionID) = agentNameID
909
910
911 execute_db_query(conn, 'UPDATE agents SET results=? WHERE session_id=?', ['', agentSessionID])
912
913 return jsonify({'success': True})
914 @app.route('/api/agents/<string:agent_name>/download', methods=['POST'])
915 def task_agent_download(agent_name):
916 """
917 Tasks the specified agent to download a file
918 """
919
920 if agent_name.lower() == "all":
921 # enumerate all target agent sessionIDs
922 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
923 else:
924 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
925
926 if not agentNameIDs or len(agentNameIDs) == 0:
927 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
928
929 if not request.json['filename']:
930 return make_response(jsonify({'error':'file name not provided'}), 404)
931
932 fileName = request.json['filename']
933 for agentNameID in agentNameIDs:
934 (agentName, agentSessionID) = agentNameID
935
936 msg = "Tasked agent to download %s" % (fileName)
937 main.agents.save_agent_log(agentSessionID, msg)
938 taskID = main.agents.add_agent_task_db(agentSessionID, 'TASK_DOWNLOAD', fileName)
939
940 return jsonify({'success': True, 'taskID': taskID})
941
942 @app.route('/api/agents/<string:agent_name>/upload', methods=['POST'])
943 def task_agent_upload(agent_name):
944 """
945 Tasks the specified agent to upload a file
946 """
947
948 if agent_name.lower() == "all":
949 # enumerate all target agent sessionIDs
950 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
951 else:
952 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
953
954 if not agentNameIDs or len(agentNameIDs) == 0:
955 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
956
957 if not request.json['data']:
958 return make_response(jsonify({'error':'file data not provided'}), 404)
959
960 if not request.json['filename']:
961 return make_response(jsonify({'error':'file name not provided'}), 404)
962
963 fileData = request.json['data']
964 fileName = request.json['filename']
965
966 rawBytes = base64.b64decode(fileData)
967
968 if len(rawBytes) > 1048576:
969 return make_response(jsonify({'error':'file size too large'}), 404)
970
971 for agentNameID in agentNameIDs:
972 (agentName, agentSessionID) = agentNameID
973
974 msg = "Tasked agent to upload %s : %s" % (fileName, hashlib.md5(rawBytes).hexdigest())
975 main.agents.save_agent_log(agentSessionID, msg)
976 data = fileName + "|" + fileData
977 taskID = main.agents.add_agent_task_db(agentSessionID, 'TASK_UPLOAD', data)
978
979 return jsonify({'success': True, 'taskID': taskID})
980
981 @app.route('/api/agents/<string:agent_name>/shell', methods=['POST'])
982 def task_agent_shell(agent_name):
983 """
984 Tasks an the specified agent_name to execute a shell command.
985
986 Takes {'command':'shell_command'}
987 """
988 if agent_name.lower() == "all":
989 # enumerate all target agent sessionIDs
990 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
991 else:
992 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
993
994 if not agentNameIDs or len(agentNameIDs) == 0:
995 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
996
997 command = request.json['command']
998
999 for agentNameID in agentNameIDs:
1000 (agentName, agentSessionID) = agentNameID
1001
1002 # add task command to agent taskings
1003 msg = "tasked agent %s to run command %s" %(agentSessionID, command)
1004 main.agents.save_agent_log(agentSessionID, msg)
1005 taskID = main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command)
1006
1007 return jsonify({'success': True, 'taskID': taskID})
1008
1009 @app.route('/api/agents/<string:agent_name>/rename', methods=['POST'])
1010 def task_agent_rename(agent_name):
1011 """
1012 Renames the specified agent.
1013
1014 Takes {'newname':'NAME'}
1015 """
1016
1017 agentNameID = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
1018
1019 if not agentNameID or len(agentNameID) == 0:
1020 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
1021
1022 (agentName, agentSessionID) = agentNameID[0]
1023 newName = request.json['newname']
1024
1025 try:
1026 result = main.agents.rename_agent(agentName, newName)
1027
1028 if not result:
1029 return make_response(jsonify({'error': 'error in renaming %s to %s, new name may have already been used' %(agentName, newName)}), 400)
1030
1031 return jsonify({'success': True})
1032
1033 except Exception:
1034 return make_response(jsonify({'error': 'error in renaming %s to %s' %(agentName, newName)}), 400)
1035
1036
1037 @app.route('/api/agents/<string:agent_name>/clear', methods=['POST', 'GET'])
1038 def task_agent_clear(agent_name):
1039 """
1040 Clears the tasking buffer for the specified agent.
1041 """
1042 if agent_name.lower() == "all":
1043 # enumerate all target agent sessionIDs
1044 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
1045 else:
1046 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
1047
1048 if not agentNameIDs or len(agentNameIDs) == 0:
1049 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
1050
1051 for agentNameID in agentNameIDs:
1052 (agentName, agentSessionID) = agentNameID
1053
1054 main.agents.clear_agent_tasks_db(agentSessionID)
1055
1056 return jsonify({'success': True})
1057
1058
1059 @app.route('/api/agents/<string:agent_name>/kill', methods=['POST', 'GET'])
1060 def task_agent_kill(agent_name):
1061 """
1062 Tasks the specified agent to exit.
1063 """
1064 if agent_name.lower() == "all":
1065 # enumerate all target agent sessionIDs
1066 agentNameIDs = execute_db_query(conn, "SELECT name,session_id FROM agents WHERE name like '%' OR session_id like '%'")
1067 else:
1068 agentNameIDs = execute_db_query(conn, 'SELECT name,session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])
1069
1070 if not agentNameIDs or len(agentNameIDs) == 0:
1071 return make_response(jsonify({'error': 'agent name %s not found' %(agent_name)}), 404)
1072
1073 for agentNameID in agentNameIDs:
1074 (agentName, agentSessionID) = agentNameID
1075
1076 # task the agent to exit
1077 msg = "tasked agent %s to exit" %(agentSessionID)
1078 main.agents.save_agent_log(agentSessionID, msg)
1079 main.agents.add_agent_task_db(agentSessionID, 'TASK_EXIT')
1080
1081 return jsonify({'success': True})
1082
1083
1084 @app.route('/api/creds', methods=['GET'])
1085 def get_creds():
1086 """
1087 Returns JSON describing the credentials stored in the backend database.
1088 """
1089 credsRaw = execute_db_query(conn, 'SELECT ID, credtype, domain, username, password, host, os, sid, notes FROM credentials')
1090 creds = []
1091
1092 for credRaw in credsRaw:
1093 [ID, credtype, domain, username, password, host, os, sid, notes] = credRaw
1094 creds.append({"ID":ID, "credtype":credtype, "domain":domain, "username":username, "password":password, "host":host, "os":os, "sid":sid, "notes":notes})
1095
1096 return jsonify({'creds' : creds})
1097
1098 @app.route('/api/creds', methods=['POST'])
1099 def add_creds():
1100 """
1101 Adds credentials to the database
1102 """
1103 if not request.json:
1104 return make_response(jsonify({'error':'request body must be valid JSON'}), 400)
1105
1106 if not 'credentials' in request.json:
1107 return make_response(jsonify({'error':'JSON body must include key "credentials"'}), 400)
1108
1109 creds = request.json['credentials']
1110
1111 if not type(creds) == list:
1112 return make_response(jsonify({'error':'credentials must be provided as a list'}), 400)
1113
1114 required_fields = ["credtype", "domain", "username", "password", "host"]
1115 optional_fields = ["OS", "notes", "sid"]
1116
1117 for cred in creds:
1118 # ensure every credential given to us has all the required fields
1119 if not all (k in cred for k in required_fields):
1120 return make_response(jsonify({'error':'invalid credential %s' %(cred)}), 400)
1121
1122 # ensure the type is either "hash" or "plaintext"
1123 if not (cred['credtype'] == u'hash' or cred['credtype'] == u'plaintext'):
1124 return make_response(jsonify({'error':'invalid credential type in %s, must be "hash" or "plaintext"' %(cred)}), 400)
1125
1126 # other than that... just assume everything is valid
1127
1128 # this would be way faster if batched but will work for now
1129 for cred in creds:
1130 # get the optional stuff, if it's there
1131 try:
1132 os = cred['os']
1133 except KeyError:
1134 os = ''
1135
1136 try:
1137 sid = cred['sid']
1138 except KeyError:
1139 sid = ''
1140
1141 try:
1142 notes = cred['notes']
1143 except KeyError:
1144 notes = ''
1145
1146 main.credentials.add_credential(
1147 cred['credtype'],
1148 cred['domain'],
1149 cred['username'],
1150 cred['password'],
1151 cred['host'],
1152 os,
1153 sid,
1154 notes
1155 )
1156
1157 return jsonify({'success': '%s credentials added' % len(creds)})
1158
1159
1160 @app.route('/api/reporting', methods=['GET'])
1161 def get_reporting():
1162 """
1163 Returns JSON describing the reporting events from the backend database.
1164 """
1165 reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting')
1166 reportingEvents = []
1167
1168 for reportingEvent in reportingRaw:
1169 [ID, name, event_type, message, time_stamp, taskID] = reportingEvent
1170 reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID})
1171
1172 return jsonify({'reporting' : reportingEvents})
1173
1174
1175 @app.route('/api/reporting/agent/<string:reporting_agent>', methods=['GET'])
1176 def get_reporting_agent(reporting_agent):
1177 """
1178 Returns JSON describing the reporting events from the backend database for
1179 the agent specified by reporting_agent.
1180 """
1181
1182 # first resolve the supplied name to a sessionID
1183 results = execute_db_query(conn, 'SELECT session_id FROM agents WHERE name=?', [reporting_agent])
1184 if results:
1185 sessionID = results[0][0]
1186 else:
1187 return jsonify({'reporting' : ''})
1188
1189 reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE name=?', [sessionID])
1190 reportingEvents = []
1191
1192 for reportingEvent in reportingRaw:
1193 [ID, name, event_type, message, time_stamp, taskID] = reportingEvent
1194 reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID})
1195
1196 return jsonify({'reporting' : reportingEvents})
1197
1198
1199 @app.route('/api/reporting/type/<string:event_type>', methods=['GET'])
1200 def get_reporting_type(event_type):
1201 """
1202 Returns JSON describing the reporting events from the backend database for
1203 the event type specified by event_type.
1204 """
1205 reportingRaw = execute_db_query(conn, 'SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE event_type=?', [event_type])
1206 reportingEvents = []
1207
1208 for reportingEvent in reportingRaw:
1209 [ID, name, event_type, message, time_stamp, taskID] = reportingEvent
1210 reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID})
1211
1212 return jsonify({'reporting' : reportingEvents})
1213
1214
1215 @app.route('/api/reporting/msg/<string:msg>', methods=['GET'])
1216 def get_reporting_msg(msg):
1217 """
1218 Returns JSON describing the reporting events from the backend database for
1219 the any messages with *msg* specified by msg.
1220 """
1221 reportingRaw = execute_db_query(conn, "SELECT ID, name, event_type, message, time_stamp, taskID FROM reporting WHERE message like ?", ['%'+msg+'%'])
1222 reportingEvents = []
1223
1224 for reportingEvent in reportingRaw:
1225 [ID, name, event_type, message, time_stamp, taskID] = reportingEvent
1226 reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID})
1227
1228 return jsonify({'reporting' : reportingEvents})
1229
1230
1231 @app.route('/api/admin/login', methods=['POST'])
1232 def server_login():
1233 """
1234 Takes a supplied username and password and returns the current API token
1235 if authentication is accepted.
1236 """
1237
1238 if not request.json or not 'username' in request.json or not 'password' in request.json:
1239 abort(400)
1240
1241 suppliedUsername = request.json['username']
1242 suppliedPassword = request.json['password']
1243
1244 # try to prevent some basic bruting
1245 time.sleep(2)
1246
1247 if suppliedUsername == username and suppliedPassword == password:
1248 return jsonify({'token': apiToken})
1249 else:
1250 return make_response('', 401)
1251
1252
1253 @app.route('/api/admin/permanenttoken', methods=['GET'])
1254 def get_server_perm_token():
1255 """
1256 Returns the 'permanent' API token for the server.
1257 """
1258 permanentToken = get_permanent_token(conn)
1259 return jsonify({'token': permanentToken})
1260
1261
1262 @app.route('/api/admin/restart', methods=['GET', 'POST', 'PUT'])
1263 def signal_server_restart():
1264 """
1265 Signal a restart for the Flask server and any Empire instance.
1266 """
1267 restart_server()
1268 return jsonify({'success': True})
1269
1270
1271 @app.route('/api/admin/shutdown', methods=['GET', 'POST', 'PUT'])
1272 def signal_server_shutdown():
1273 """
1274 Signal a restart for the Flask server and any Empire instance.
1275 """
1276 shutdown_server()
1277 return jsonify({'success': True})
1278
1279
1280 if not os.path.exists('./data/empire-chain.pem'):
1281 print "[!] Error: cannot find certificate ./data/empire-chain.pem"
1282 sys.exit()
1283
1284
1285 def shutdown_server():
1286 """
1287 Shut down the Flask server and any Empire instance gracefully.
1288 """
1289 global serverExitCommand
1290
1291 if suppress:
1292 # repair stdout
1293 sys.stdout.close()
1294 sys.stdout = oldStdout
1295
1296 print "\n * Shutting down Empire RESTful API"
1297
1298 if conn:
1299 conn.close()
1300
1301 if suppress:
1302 print " * Shutting down the Empire instance"
1303 main.shutdown()
1304
1305 serverExitCommand = 'shutdown'
1306
1307 func = request.environ.get('werkzeug.server.shutdown')
1308 if func is not None:
1309 func()
1310
1311
1312 def restart_server():
1313 """
1314 Restart the Flask server and any Empire instance.
1315 """
1316 global serverExitCommand
1317
1318 shutdown_server()
1319
1320 serverExitCommand = 'restart'
1321
1322
1323 def signal_handler(signal, frame):
1324 """
1325 Overrides the keyboardinterrupt signal handler so we can gracefully shut everything down.
1326 """
1327
1328 global serverExitCommand
1329
1330 with app.test_request_context():
1331 shutdown_server()
1332
1333 serverExitCommand = 'shutdown'
1334
1335 # repair the original signal handler
1336 import signal
1337 signal.signal(signal.SIGINT, signal.default_int_handler)
1338 sys.exit()
1339
1340 try:
1341 signal.signal(signal.SIGINT, signal_handler)
1342 except ValueError:
1343 pass
1344
1345 # wrap the Flask connection in SSL and start it
1346 certPath = os.path.abspath("./data/")
1347
1348 # support any version of tls
1349 pyversion = sys.version_info
1350 if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13:
1351 proto = ssl.PROTOCOL_TLS
1352 elif pyversion[0] >= 3:
1353 proto = ssl.PROTOCOL_TLS
1354 else:
1355 proto = ssl.PROTOCOL_SSLv23
1356
1357 context = ssl.SSLContext(proto)
1358 context.load_cert_chain("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath))
1359 app.run(host='0.0.0.0', port=int(port), ssl_context=context, threaded=True)
1360
1361
1362if __name__ == '__main__':
1363
1364 parser = argparse.ArgumentParser()
1365
1366 generalGroup = parser.add_argument_group('General Options')
1367 generalGroup.add_argument('--debug', nargs='?', const='1', help='Debug level for output (default of 1, 2 for msg display).')
1368 generalGroup.add_argument('-v', '--version', action='store_true', help='Display current Empire version.')
1369 generalGroup.add_argument('-r','--resource', nargs=1, help='Run the Empire commands in the specified resource file after startup.')
1370
1371 cliGroup = parser.add_argument_group('CLI Payload Options')
1372 cliGroup.add_argument('-l', '--listener', nargs='?', const="list", help='Display listener options. Displays all listeners if nothing is specified.')
1373 cliGroup.add_argument('-s', '--stager', nargs='?', const="list", help='Specify a stager to generate. Lists all stagers if none is specified.')
1374 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.")
1375
1376 restGroup = parser.add_argument_group('RESTful API Options')
1377 launchGroup = restGroup.add_mutually_exclusive_group()
1378 launchGroup.add_argument('--rest', action='store_true', help='Run Empire and the RESTful API.')
1379 launchGroup.add_argument('--headless', action='store_true', help='Run Empire and the RESTful API headless without the usual interface.')
1380 restGroup.add_argument('--restport', type=int, nargs=1, help='Port to run the Empire RESTful API on.')
1381 restGroup.add_argument('--username', nargs=1, help='Start the RESTful API with the specified username instead of pulling from empire.db')
1382 restGroup.add_argument('--password', nargs=1, help='Start the RESTful API with the specified password instead of pulling from empire.db')
1383
1384 args = parser.parse_args()
1385 database_check_docker()
1386
1387 if not args.restport:
1388 args.restport = '1337'
1389 else:
1390 args.restport = args.restport[0]
1391
1392 if args.version:
1393 print empire.VERSION
1394
1395 elif args.rest:
1396 # start an Empire instance and RESTful API
1397 main = empire.MainMenu(args=args)
1398 def thread_api(empireMenu):
1399
1400 try:
1401 start_restful_api(empireMenu=empireMenu, suppress=False, username=args.username, password=args.password, port=args.restport)
1402 except SystemExit as e:
1403 pass
1404
1405 thread = helpers.KThread(target=thread_api, args=(main,))
1406 thread.daemon = True
1407 thread.start()
1408 sleep(2)
1409 main.cmdloop()
1410
1411 elif args.headless:
1412 # start an Empire instance and RESTful API and suppress output
1413 main = empire.MainMenu(args=args)
1414
1415 try:
1416 start_restful_api(empireMenu=main, suppress=True, username=args.username, password=args.password, port=args.restport)
1417 except SystemExit as e:
1418 pass
1419
1420 else:
1421 # normal execution
1422 main = empire.MainMenu(args=args)
1423 main.cmdloop()
1424
1425 sys.exit()