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
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 ) :
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 ' )
mssql_parser . add_argument ( " --port " , default = 1433 , type = int , dest = ' mssql_port ' , 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 ' )
2016-12-15 07:28:00 +00:00
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 " )
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
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 :
2017-04-03 15:25:05 +00:00
self . conn = tds . MSSQL ( self . host , self . args . mssql_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 ' " )
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 printRepliesCME ( self ) :
2017-04-03 15:25:05 +00:00
print ( self . _MSSQL__rowsPrinter )
2016-12-15 07:28:00 +00:00
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-03 15:25:05 +00:00
#tds.MSSQL.printReplies = printRepliesCME