Added /empire/api/admin/login to retrieve the current server token for auth

The api username defaults to 'empireadmin' and the password is randomly generated and stored in empire.db
The username/password can be modified with the ./empire --username X --password Y flags
1.6
Harmj0y 2016-03-22 19:28:23 -04:00
parent 446a004cc1
commit 894fe44700
2 changed files with 96 additions and 18 deletions

91
empire
View File

@ -58,11 +58,26 @@ def refresh_api_token(conn):
# generate a randomized API token # generate a randomized API token
apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40)) apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
execute_db_query(conn, "UPDATE config SET api_token=?", [apiToken]) execute_db_query(conn, "UPDATE config SET api_current_token=?", [apiToken])
return apiToken return apiToken
def get_permanent_token(conn):
"""
Returns the permanent API token stored in empire.db.
If one doesn't exist, it will generate one and store it before returning.
"""
permanentToken = execute_db_query(conn, "SELECT api_permanent_token FROM config")[0]
if not permanentToken[0]:
permanentToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
execute_db_query(conn, "UPDATE config SET api_permanent_token=?", [permanentToken])
return permanentToken[0]
#################################################################### ####################################################################
# #
# The Empire RESTful API. # The Empire RESTful API.
@ -101,16 +116,36 @@ def refresh_api_token(conn):
# GET http://localhost:1337/empire/api/reporting/msg/Z return all logged events matching message Z, wildcards accepted # GET http://localhost:1337/empire/api/reporting/msg/Z return all logged events matching message Z, wildcards accepted
# #
# GET http://localhost:1337/empire/api/admin/shutdown shutdown the RESTful API # GET http://localhost:1337/empire/api/admin/shutdown shutdown the RESTful API
# GET http://localhost:1337/empire/api/admin/login retrieve the API token given the correct username and password
# GET http://localhost:1337/empire/api/admin/permanenttoken retrieve the permanent API token, generating/storing one if it doesn't already exist
# #
#################################################################### ####################################################################
def start_restful_api(startEmpire=False, suppress=False, port=1337): def start_restful_api(startEmpire=False, suppress=False, username=None, password=None, port=1337):
''' """
Kick off the RESTful API with the given parameters.
''' startEmpire - start a complete Empire server in the backend as well
suppress - suppress most console output
username - optional username to use for the API, otherwise pulls from the empire.db config
password - optional password to use for the API, otherwise pulls from the empire.db config
port - port to start the API on, defaults to 1337 ;)
"""
app = Flask(__name__) app = Flask(__name__)
conn = database_connect()
# if a username/password were not supplied, use the creds stored in the db
(dbUsername, dbPassword) = execute_db_query(conn, "SELECT api_username, api_password FROM config")[0]
if not username:
username = dbUsername
if not password:
password = dbPassword
class Namespace: class Namespace:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.__dict__.update(kwargs) self.__dict__.update(kwargs)
@ -128,7 +163,6 @@ def start_restful_api(startEmpire=False, suppress=False, port=1337):
# if we just want the RESTful API, i.e. no listener/etc. startup # if we just want the RESTful API, i.e. no listener/etc. startup
main = empire.MainMenu(args=args, restAPI=True) main = empire.MainMenu(args=args, restAPI=True)
conn = database_connect()
print " * Starting Empire RESTful API on port: %s" %(port) print " * Starting Empire RESTful API on port: %s" %(port)
@ -136,6 +170,7 @@ def start_restful_api(startEmpire=False, suppress=False, port=1337):
apiToken = refresh_api_token(conn) apiToken = refresh_api_token(conn)
print " * RESTful API token: %s" %(apiToken) print " * RESTful API token: %s" %(apiToken)
permanentApiToken = get_permanent_token(conn)
tokenAllowed = re.compile("^[0-9a-z]{40}") tokenAllowed = re.compile("^[0-9a-z]{40}")
oldStdout = sys.stdout oldStdout = sys.stdout
@ -148,13 +183,14 @@ def start_restful_api(startEmpire=False, suppress=False, port=1337):
sys.stdout = open(os.devnull, 'w') sys.stdout = open(os.devnull, 'w')
# validate API token before every request # validate API token before every request except for the login URI
@app.before_request @app.before_request
def check_token(): def check_token():
if request.path != '/empire/api/admin/login':
token = request.args.get('token') token = request.args.get('token')
if (not token) or (not tokenAllowed.match(token)): if (not token) or (not tokenAllowed.match(token)):
return make_response('', 403) return make_response('', 403)
if token != apiToken: if (token != apiToken) and (token != permanentApiToken):
return make_response('', 403) return make_response('', 403)
@ -183,8 +219,8 @@ def start_restful_api(startEmpire=False, suppress=False, port=1337):
""" """
configRaw = execute_db_query(conn, 'SELECT * FROM config') configRaw = execute_db_query(conn, 'SELECT * FROM config')
[staging_key, stage0_uri, stage1_uri, stage2_uri, default_delay, default_jitter, default_profile, default_cert_path, default_port, install_path, server_version, ip_whitelist, ip_blacklist, default_lost_limit, autorun_command, autorun_data, api_token] = configRaw[0] [staging_key, stage0_uri, stage1_uri, stage2_uri, default_delay, default_jitter, default_profile, default_cert_path, default_port, install_path, server_version, ip_whitelist, ip_blacklist, default_lost_limit, autorun_command, autorun_data, current_api_token, permanent_api_token] = configRaw[0]
config = {"version":empire.VERSION, "staging_key":staging_key, "stage0_uri":stage0_uri, "stage1_uri":stage1_uri, "stage2_uri":stage2_uri, "default_delay":default_delay, "default_jitter":default_jitter, "default_profile":default_profile, "default_cert_path":default_cert_path, "default_port":default_port, "install_path":install_path, "server_version":server_version, "ip_whitelist":ip_whitelist, "ip_blacklist":ip_blacklist, "default_lost_limit":default_lost_limit, "autorun_command":autorun_command, "autorun_data":autorun_data, "api_token":api_token} config = {"version":empire.VERSION, "staging_key":staging_key, "stage0_uri":stage0_uri, "stage1_uri":stage1_uri, "stage2_uri":stage2_uri, "default_delay":default_delay, "default_jitter":default_jitter, "default_profile":default_profile, "default_cert_path":default_cert_path, "default_port":default_port, "install_path":install_path, "server_version":server_version, "ip_whitelist":ip_whitelist, "ip_blacklist":ip_blacklist, "default_lost_limit":default_lost_limit, "autorun_command":autorun_command, "autorun_data":autorun_data, "current_api_token":current_api_token, "permanent_api_token":permanent_api_token}
return jsonify({'config': config}) return jsonify({'config': config})
@ -591,6 +627,37 @@ def start_restful_api(startEmpire=False, suppress=False, port=1337):
return jsonify({'result': True}) return jsonify({'result': True})
@app.route('/empire/api/admin/login', methods=['POST'])
def server_login():
"""
Takes a supplied username and password and returns the current API token
if authentication is accepted.
"""
if not request.json or not 'username' in request.json or not 'password' in request.json:
abort(400)
suppliedUsername = request.json['username']
suppliedPassword = request.json['password']
# try to prevent some basic bruting
time.sleep(2)
if suppliedUsername == username and suppliedPassword == password:
return jsonify({'token': apiToken})
else:
return make_response('', 401)
@app.route('/empire/api/admin/permanenttoken', methods=['GET'])
def get_server_perm_token():
"""
Returns the 'permanent' API token for the server.
"""
permanentToken = get_permanent_token(conn)
return jsonify({'token': permanentToken})
if not os.path.exists('./data/empire.pem'): if not os.path.exists('./data/empire.pem'):
print "[!] Error: cannot find certificate ./data/empire.pem" print "[!] Error: cannot find certificate ./data/empire.pem"
sys.exit() sys.exit()
@ -645,6 +712,8 @@ if __name__ == '__main__':
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('--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('--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()
@ -653,11 +722,11 @@ if __name__ == '__main__':
elif args.rest: elif args.rest:
# start just the RESTful API # start just the RESTful API
start_restful_api(startEmpire=False, suppress=False, port=1337) start_restful_api(startEmpire=False, suppress=False, username=args.username, password=args.password, port=1337)
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, port=1337) start_restful_api(startEmpire=True, suppress=True, username=args.username, password=args.password, port=1337)
else: else:
# normal execution # normal execution

View File

@ -69,9 +69,15 @@ IP_WHITELIST = ""
# format is 192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8 # format is 192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8
IP_BLACKLIST = "" IP_BLACKLIST = ""
#number of times an agent will call back without an answer prior to exiting # number of times an agent will call back without an answer prior to exiting
DEFAULT_LOST_LIMIT = 60 DEFAULT_LOST_LIMIT = 60
# default credentials used to log into the RESTful API
API_USERNAME = "empireadmin"
API_PASSWORD = ''.join(random.sample(string.ascii_letters + string.digits + punctuation, 32))
# the 'permanent' API token (doesn't change)
API_PERMANENT_TOKEN = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
################################################### ###################################################
@ -105,11 +111,14 @@ c.execute('''CREATE TABLE config (
"default_lost_limit" integer, "default_lost_limit" integer,
"autorun_command" text, "autorun_command" text,
"autorun_data" text, "autorun_data" text,
"api_token" text "api_username" text,
"api_password" text,
"api_current_token" text,
"api_permanent_token" text
)''') )''')
# kick off the config component of the database # kick off the config component of the database
c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?)", (STAGING_KEY,STAGE0_URI,STAGE1_URI,STAGE2_URI,DEFAULT_DELAY,DEFAULT_JITTER,DEFAULT_PROFILE,DEFAULT_CERT_PATH,DEFAULT_PORT,INSTALL_PATH,SERVER_VERSION,IP_WHITELIST,IP_BLACKLIST, DEFAULT_LOST_LIMIT, "", "", "")) c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (STAGING_KEY,STAGE0_URI,STAGE1_URI,STAGE2_URI,DEFAULT_DELAY,DEFAULT_JITTER,DEFAULT_PROFILE,DEFAULT_CERT_PATH,DEFAULT_PORT,INSTALL_PATH,SERVER_VERSION,IP_WHITELIST,IP_BLACKLIST, DEFAULT_LOST_LIMIT, "", "", API_USERNAME, API_PASSWORD, "", API_PERMANENT_TOKEN))
c.execute('''CREATE TABLE "agents" ( c.execute('''CREATE TABLE "agents" (
"id" integer PRIMARY KEY, "id" integer PRIMARY KEY,