2019-11-11 10:06:39 +00:00
|
|
|
#!/usr/bin/env python3
|
2022-07-18 23:59:14 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2016-05-16 23:48:31 +00:00
|
|
|
import cmd
|
|
|
|
import sqlite3
|
|
|
|
import sys
|
|
|
|
import os
|
2017-11-02 09:43:08 +00:00
|
|
|
import requests
|
|
|
|
from time import sleep
|
|
|
|
from terminaltables import AsciiTable
|
2019-11-10 21:42:04 +00:00
|
|
|
import configparser
|
2016-12-15 07:28:00 +00:00
|
|
|
from cme.loaders.protocol_loader import protocol_loader
|
2017-11-02 09:43:08 +00:00
|
|
|
from requests import ConnectionError
|
2022-09-12 12:12:57 +00:00
|
|
|
import csv
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
# The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
|
|
|
|
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
|
|
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|
|
|
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
class UserExitedProto(Exception):
|
|
|
|
pass
|
|
|
|
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
class DatabaseNavigator(cmd.Cmd):
|
|
|
|
|
|
|
|
def __init__(self, main_menu, database, proto):
|
|
|
|
cmd.Cmd.__init__(self)
|
|
|
|
|
|
|
|
self.main_menu = main_menu
|
|
|
|
self.config = main_menu.config
|
|
|
|
self.proto = proto
|
|
|
|
self.db = database
|
|
|
|
self.prompt = 'cmedb ({})({}) > '.format(main_menu.workspace, proto)
|
|
|
|
|
|
|
|
def do_back(self, line):
|
|
|
|
raise UserExitedProto
|
|
|
|
|
|
|
|
def do_exit(self, line):
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
def print_table(self, data, title=None):
|
2019-11-12 21:39:26 +00:00
|
|
|
print("")
|
2017-11-02 09:43:08 +00:00
|
|
|
table = AsciiTable(data)
|
|
|
|
if title:
|
|
|
|
table.title = title
|
2019-11-12 21:39:26 +00:00
|
|
|
print(table.table)
|
|
|
|
print("")
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
def do_export(self, line):
|
|
|
|
if not line:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] not enough arguments")
|
2017-11-02 09:43:08 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
line = line.split()
|
|
|
|
|
|
|
|
if line[0].lower() == 'creds':
|
2018-02-19 14:47:12 +00:00
|
|
|
if len(line) < 3:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] invalid arguments, export creds <plaintext|hashes|both|csv> <filename>")
|
2018-02-19 14:47:12 +00:00
|
|
|
return
|
2017-11-02 09:43:08 +00:00
|
|
|
if line[1].lower() == 'plaintext':
|
|
|
|
creds = self.db.get_credentials(credtype="plaintext")
|
|
|
|
elif line[1].lower() == 'hashes':
|
|
|
|
creds = self.db.get_credentials(credtype="hash")
|
2018-02-19 14:47:12 +00:00
|
|
|
else:
|
|
|
|
creds = self.db.get_credentials()
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
with open(os.path.expanduser(line[2]), 'w') as export_file:
|
|
|
|
for cred in creds:
|
2018-02-19 14:47:12 +00:00
|
|
|
credid, domain, user, password, credtype, fromhost = cred
|
|
|
|
if line[1].lower() == 'csv':
|
|
|
|
export_file.write('{},{},{},{},{},{}\n'.format(credid,domain,user,password,credtype,fromhost))
|
|
|
|
else:
|
|
|
|
export_file.write('{}\n'.format(password))
|
2019-11-12 21:39:26 +00:00
|
|
|
print('[+] creds exported')
|
2018-02-19 14:47:12 +00:00
|
|
|
|
|
|
|
elif line[0].lower() == 'hosts':
|
|
|
|
if len(line) < 2:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] invalid arguments, export hosts <filename>")
|
2018-02-19 14:47:12 +00:00
|
|
|
return
|
|
|
|
hosts = self.db.get_computers()
|
|
|
|
with open(os.path.expanduser(line[1]), 'w') as export_file:
|
|
|
|
for host in hosts:
|
|
|
|
hostid,ipaddress,hostname,domain,opsys,dc = host
|
|
|
|
export_file.write('{},{},{},{},{},{}\n'.format(hostid,ipaddress,hostname,domain,opsys,dc))
|
2019-11-12 21:39:26 +00:00
|
|
|
print('[+] hosts exported')
|
2018-02-19 14:47:12 +00:00
|
|
|
|
2022-09-09 17:04:14 +00:00
|
|
|
elif line[0].lower() == 'shares':
|
|
|
|
if len(line) < 3:
|
|
|
|
print("[-] invalid arguments, export shares <simple|detailed> <filename>")
|
|
|
|
return
|
|
|
|
|
|
|
|
if line[1].lower() == 'simple':
|
|
|
|
shares = self.db.get_shares()
|
|
|
|
with open(os.path.expanduser(line[2]), 'w') as export_file:
|
2022-09-12 12:12:57 +00:00
|
|
|
shareCSV = csv.writer(export_file, delimiter=";", quoting=csv.QUOTE_ALL, lineterminator='\n')
|
|
|
|
csv_header = ["id","computerid","userid","name","remark","read","write"]
|
|
|
|
shareCSV.writerow(csv_header)
|
2022-09-09 17:04:14 +00:00
|
|
|
#id|computerid|userid|name|remark|read|write
|
|
|
|
for share in shares:
|
|
|
|
shareid,hostid,userid,sharename,shareremark,read,write = share
|
2022-09-12 12:12:57 +00:00
|
|
|
shareCSV.writerow([shareid,hostid,userid,sharename,shareremark,read,write])
|
2022-09-09 17:04:14 +00:00
|
|
|
print('[+] shares exported')
|
2022-09-09 17:31:00 +00:00
|
|
|
|
2022-09-09 17:04:14 +00:00
|
|
|
elif line[1].lower() == 'detailed': #Detailed view gets hostsname, and usernames, and true false statement
|
|
|
|
shares = self.db.get_shares()
|
|
|
|
#id|computerid|userid|name|remark|read|write
|
|
|
|
with open(os.path.expanduser(line[2]), 'w') as export_file:
|
2022-09-12 12:12:57 +00:00
|
|
|
shareCSV = csv.writer(export_file, delimiter=";", quoting=csv.QUOTE_ALL, lineterminator='\n')
|
|
|
|
csv_header = ["id","computerid","userid","name","remark","read","write"]
|
|
|
|
shareCSV.writerow(csv_header)
|
2022-09-09 17:04:14 +00:00
|
|
|
for share in shares:
|
|
|
|
shareid,hostid,userid,sharename,shareremark,read,write = share
|
2022-09-12 12:12:57 +00:00
|
|
|
shareCSV.writerow([shareid,self.get_host(hostid),self.get_user(userid),sharename,shareremark,bool(read),bool(write)])
|
2022-09-09 17:04:14 +00:00
|
|
|
print('[+] shares exported')
|
|
|
|
|
|
|
|
else:
|
|
|
|
print("[-] invalid arguments, export shares <simple|detailed> <filename>")
|
|
|
|
return
|
|
|
|
|
|
|
|
else:
|
|
|
|
print('[-] invalid argument, specify creds, hosts or shares')
|
|
|
|
|
|
|
|
# Return username from ID
|
|
|
|
def get_user(self,req_id: int) -> str:
|
|
|
|
users = self.db.get_users()
|
|
|
|
#id|domain|username|password|credtype|pillaged_from_computerid
|
|
|
|
for u in users:
|
|
|
|
id,domain,username,password,credtype,pillaged_from_computerid = u
|
|
|
|
if req_id == id:
|
|
|
|
return '{}\\{}'.format(domain,username)
|
|
|
|
return "USER NOT FOUND"
|
|
|
|
|
|
|
|
# Return hostname from ID
|
|
|
|
def get_host(self,req_id: int) -> str:
|
|
|
|
hosts = self.db.get_computers()
|
|
|
|
#id|ip|hostname|domain|os|dc|smbv1|signing
|
|
|
|
for h in hosts:
|
|
|
|
id,ip,hostname,domain,os,dc,smbv1,signing = h
|
|
|
|
if req_id == id:
|
|
|
|
return hostname
|
|
|
|
return "HOST NOT FOUND"
|
|
|
|
|
|
|
|
|
2018-02-19 14:47:12 +00:00
|
|
|
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
def do_import(self, line):
|
|
|
|
if not line:
|
|
|
|
return
|
|
|
|
|
|
|
|
if line == 'empire':
|
|
|
|
headers = {'Content-Type': 'application/json'}
|
|
|
|
|
|
|
|
# Pull the username and password from the config file
|
|
|
|
payload = {'username': self.config.get('Empire', 'username'),
|
|
|
|
'password': self.config.get('Empire', 'password')}
|
|
|
|
|
|
|
|
# Pull the host and port from the config file
|
|
|
|
base_url = 'https://{}:{}'.format(self.config.get('Empire', 'api_host'), self.config.get('Empire', 'api_port'))
|
|
|
|
|
|
|
|
try:
|
|
|
|
r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False)
|
|
|
|
if r.status_code == 200:
|
|
|
|
token = r.json()['token']
|
|
|
|
|
|
|
|
url_params = {'token': token}
|
|
|
|
r = requests.get(base_url + '/api/creds', headers=headers, params=url_params, verify=False)
|
|
|
|
creds = r.json()
|
|
|
|
|
|
|
|
for cred in creds['creds']:
|
|
|
|
if cred['credtype'] == 'token' or cred['credtype'] == 'krbtgt' or cred['username'].endswith('$'):
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.db.add_credential(cred['credtype'], cred['domain'], cred['username'], cred['password'])
|
|
|
|
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[+] Empire credential import successful")
|
2017-11-02 09:43:08 +00:00
|
|
|
else:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] Error authenticating to Empire's RESTful API server!")
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
except ConnectionError as e:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] Unable to connect to Empire's RESTful API server: {}".format(e))
|
2017-11-02 09:43:08 +00:00
|
|
|
|
|
|
|
def complete_import(self, text, line, begidx, endidx):
|
|
|
|
"Tab-complete 'import' commands."
|
|
|
|
|
|
|
|
commands = ["empire", "metasploit"]
|
|
|
|
|
|
|
|
mline = line.partition(' ')[2]
|
|
|
|
offs = len(mline) - len(text)
|
|
|
|
return [s[offs:] for s in commands if s.startswith(mline)]
|
|
|
|
|
|
|
|
def complete_export(self, text, line, begidx, endidx):
|
|
|
|
"Tab-complete 'creds' commands."
|
|
|
|
|
|
|
|
commands = ["creds", "plaintext", "hashes"]
|
|
|
|
|
|
|
|
mline = line.partition(' ')[2]
|
|
|
|
offs = len(mline) - len(text)
|
|
|
|
return [s[offs:] for s in commands if s.startswith(mline)]
|
|
|
|
|
|
|
|
|
|
|
|
class CMEDBMenu(cmd.Cmd):
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def __init__(self, config_path):
|
2016-05-16 23:48:31 +00:00
|
|
|
cmd.Cmd.__init__(self)
|
2017-03-27 21:09:36 +00:00
|
|
|
|
|
|
|
self.config_path = config_path
|
2016-05-16 23:48:31 +00:00
|
|
|
|
|
|
|
try:
|
2019-11-10 21:42:04 +00:00
|
|
|
self.config = configparser.ConfigParser()
|
2017-03-27 21:09:36 +00:00
|
|
|
self.config.read(self.config_path)
|
2016-05-16 23:48:31 +00:00
|
|
|
except Exception as e:
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] Error reading cme.conf: {}".format(e))
|
2016-06-09 03:44:45 +00:00
|
|
|
sys.exit(1)
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
self.workspace_dir = os.path.expanduser('~/.cme/workspaces')
|
|
|
|
self.conn = None
|
|
|
|
self.p_loader = protocol_loader()
|
|
|
|
self.protocols = self.p_loader.get_protocols()
|
|
|
|
|
|
|
|
self.workspace = self.config.get('CME', 'workspace')
|
|
|
|
self.do_workspace(self.workspace)
|
|
|
|
|
|
|
|
self.db = self.config.get('CME', 'last_used_db')
|
|
|
|
if self.db:
|
|
|
|
self.do_proto(self.db)
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def open_proto_db(self, db_path):
|
2017-11-02 09:43:08 +00:00
|
|
|
# Set the database connection to autocommit w/ isolation level
|
2016-12-15 07:28:00 +00:00
|
|
|
self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
|
|
self.conn.text_factory = str
|
|
|
|
self.conn.isolation_level = None
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
def write_configfile(self):
|
2019-11-12 21:39:26 +00:00
|
|
|
with open(self.config_path, 'w') as configfile:
|
2017-03-27 21:09:36 +00:00
|
|
|
self.config.write(configfile)
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def do_proto(self, proto):
|
2017-11-02 09:43:08 +00:00
|
|
|
if not proto:
|
|
|
|
return
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
proto_db_path = os.path.join(self.workspace_dir, self.workspace, proto + '.db')
|
|
|
|
if os.path.exists(proto_db_path):
|
|
|
|
self.open_proto_db(proto_db_path)
|
2017-11-02 09:43:08 +00:00
|
|
|
db_nav_object = self.p_loader.load_protocol(self.protocols[proto]['nvpath'])
|
|
|
|
db_object = self.p_loader.load_protocol(self.protocols[proto]['dbpath'])
|
2017-03-27 21:09:36 +00:00
|
|
|
self.config.set('CME', 'last_used_db', proto)
|
|
|
|
self.write_configfile()
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
try:
|
2017-11-02 09:43:08 +00:00
|
|
|
proto_menu = getattr(db_nav_object, 'navigator')(self, getattr(db_object, 'database')(self.conn), proto)
|
2017-03-27 21:09:36 +00:00
|
|
|
proto_menu.cmdloop()
|
|
|
|
except UserExitedProto:
|
|
|
|
pass
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def do_workspace(self, line):
|
2017-11-02 09:43:08 +00:00
|
|
|
if not line:
|
|
|
|
return
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
line = line.strip()
|
|
|
|
|
|
|
|
if line.split()[0] == 'create':
|
|
|
|
new_workspace = line.split()[1].strip()
|
|
|
|
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[*] Creating workspace '{}'".format(new_workspace))
|
2017-03-27 21:09:36 +00:00
|
|
|
os.mkdir(os.path.join(self.workspace_dir, new_workspace))
|
|
|
|
|
|
|
|
for protocol in self.protocols.keys():
|
|
|
|
try:
|
|
|
|
protocol_object = self.p_loader.load_protocol(self.protocols[protocol]['dbpath'])
|
|
|
|
except KeyError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
proto_db_path = os.path.join(self.workspace_dir, new_workspace, protocol + '.db')
|
|
|
|
|
|
|
|
if not os.path.exists(proto_db_path):
|
2019-11-12 21:39:26 +00:00
|
|
|
print('[*] Initializing {} protocol database'.format(protocol.upper()))
|
2017-03-27 21:09:36 +00:00
|
|
|
conn = sqlite3.connect(proto_db_path)
|
|
|
|
c = conn.cursor()
|
|
|
|
|
|
|
|
# try to prevent some of the weird sqlite I/O errors
|
|
|
|
c.execute('PRAGMA journal_mode = OFF')
|
|
|
|
c.execute('PRAGMA foreign_keys = 1')
|
|
|
|
|
|
|
|
getattr(protocol_object, 'database').db_schema(c)
|
|
|
|
|
|
|
|
# commit the changes and close everything off
|
|
|
|
conn.commit()
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
self.do_workspace(new_workspace)
|
|
|
|
|
|
|
|
elif os.path.exists(os.path.join(self.workspace_dir, line)):
|
|
|
|
self.config.set('CME', 'workspace', line)
|
|
|
|
self.write_configfile()
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
self.workspace = line
|
2017-05-08 03:16:18 +00:00
|
|
|
self.prompt = 'cmedb ({}) > '.format(line)
|
2016-05-16 23:48:31 +00:00
|
|
|
|
|
|
|
def do_exit(self, line):
|
|
|
|
sys.exit(0)
|
|
|
|
|
2017-11-02 09:43:08 +00:00
|
|
|
|
2016-06-04 05:42:26 +00:00
|
|
|
def main():
|
2016-12-15 07:28:00 +00:00
|
|
|
config_path = os.path.expanduser('~/.cme/cme.conf')
|
2016-06-04 07:13:38 +00:00
|
|
|
|
2016-06-09 03:44:45 +00:00
|
|
|
if not os.path.exists(config_path):
|
2019-11-12 21:39:26 +00:00
|
|
|
print("[-] Unable to find config file")
|
2016-05-16 23:48:31 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
try:
|
2017-11-02 09:43:08 +00:00
|
|
|
cmedbnav = CMEDBMenu(config_path)
|
2016-05-16 23:48:31 +00:00
|
|
|
cmedbnav.cmdloop()
|
|
|
|
except KeyboardInterrupt:
|
2016-12-15 07:28:00 +00:00
|
|
|
pass
|