Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/clients/mssqlrelayclient.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# MSSQL (TDS) Protocol Client
8#
9# Author:
10# Alberto Solino (@agsolino)
11# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
12#
13# Description:
14# MSSQL client for relaying NTLMSSP authentication to MSSQL servers
15#
16# ToDo:
17# [ ] Handle SQL Authentication
18#
19import random
20import string
21from struct import unpack
23from impacket import LOG
24from impacket.examples.ntlmrelayx.clients import ProtocolClient
25from impacket.tds import MSSQL, DummyPrint, TDS_ENCRYPT_REQ, TDS_ENCRYPT_OFF, TDS_PRE_LOGIN, TDS_LOGIN, TDS_INIT_LANG_FATAL, \
26 TDS_ODBC_ON, TDS_INTEGRATED_SECURITY_ON, TDS_LOGIN7, TDS_SSPI, TDS_LOGINACK_TOKEN
27from impacket.ntlm import NTLMAuthChallenge
28from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED
29from impacket.spnego import SPNEGO_NegTokenResp
31try:
32 from OpenSSL import SSL
33except Exception:
34 LOG.critical("pyOpenSSL is not installed, can't continue")
36PROTOCOL_CLIENT_CLASS = "MSSQLRelayClient"
38class MYMSSQL(MSSQL):
39 def __init__(self, address, port=1433, rowsPrinter=DummyPrint()):
40 MSSQL.__init__(self,address, port, rowsPrinter)
41 self.resp = None
42 self.sessionData = {}
44 def initConnection(self):
45 self.connect()
46 #This is copied from tds.py
47 resp = self.preLogin()
48 if resp['Encryption'] == TDS_ENCRYPT_REQ or resp['Encryption'] == TDS_ENCRYPT_OFF:
49 LOG.debug("Encryption required, switching to TLS")
51 # Switching to TLS now
52 ctx = SSL.Context(SSL.TLSv1_METHOD)
53 ctx.set_cipher_list('RC4, AES256')
54 tls = SSL.Connection(ctx,None)
55 tls.set_connect_state()
56 while True:
57 try:
58 tls.do_handshake()
59 except SSL.WantReadError:
60 data = tls.bio_read(4096)
61 self.sendTDS(TDS_PRE_LOGIN, data,0)
62 tds = self.recvTDS()
63 tls.bio_write(tds['Data'])
64 else:
65 break
67 # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement,
68 # Transport Layer Security(TLS), limit data fragments to 16k in size.
69 self.packetSize = 16*1024-1
70 self.tlsSocket = tls
71 self.resp = resp
72 return True
74 def sendNegotiate(self,negotiateMessage):
75 #Also partly copied from tds.py
76 login = TDS_LOGIN()
78 login['HostName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le')
79 login['AppName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le')
80 login['ServerName'] = self.server.encode('utf-16le')
81 login['CltIntName'] = login['AppName']
82 login['ClientPID'] = random.randint(0,1024)
83 login['PacketSize'] = self.packetSize
84 login['OptionFlags2'] = TDS_INIT_LANG_FATAL | TDS_ODBC_ON | TDS_INTEGRATED_SECURITY_ON
86 # NTLMSSP Negotiate
87 login['SSPI'] = negotiateMessage
88 login['Length'] = len(login.getData())
90 # Send the NTLMSSP Negotiate
91 self.sendTDS(TDS_LOGIN7, login.getData())
93 # According to the specs, if encryption is not required, we must encrypt just
94 # the first Login packet :-o
95 if self.resp['Encryption'] == TDS_ENCRYPT_OFF:
96 self.tlsSocket = None
98 tds = self.recvTDS()
99 self.sessionData['NTLM_CHALLENGE'] = tds
101 challenge = NTLMAuthChallenge()
102 challenge.fromString(tds['Data'][3:])
103 #challenge.dump()
105 return challenge
107 def sendAuth(self,authenticateMessageBlob, serverChallenge=None):
108 if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
109 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob)
110 token = respToken2['ResponseToken']
111 else:
112 token = authenticateMessageBlob
114 self.sendTDS(TDS_SSPI, token)
115 tds = self.recvTDS()
116 self.replies = self.parseReply(tds['Data'])
117 if TDS_LOGINACK_TOKEN in self.replies:
118 #Once we are here, there is a full connection and we can
119 #do whatever the current user has rights to do
120 self.sessionData['AUTH_ANSWER'] = tds
121 return None, STATUS_SUCCESS
122 else:
123 self.printReplies()
124 return None, STATUS_ACCESS_DENIED
126 def close(self):
127 return self.disconnect()
130class MSSQLRelayClient(ProtocolClient):
131 PLUGIN_NAME = "MSSQL"
133 def __init__(self, serverConfig, targetHost, targetPort = 1433, extendedSecurity=True ):
134 ProtocolClient.__init__(self, serverConfig, targetHost, targetPort, extendedSecurity)
135 self.extendedSecurity = extendedSecurity
137 self.domainIp = None
138 self.machineAccount = None
139 self.machineHashes = None
141 def initConnection(self):
142 self.session = MYMSSQL(self.targetHost, self.targetPort)
143 self.session.initConnection()
144 return True
146 def keepAlive(self):
147 # Don't know yet what needs to be done for TDS
148 pass
150 def killConnection(self):
151 if self.session is not None:
152 self.session.disconnect()
153 self.session = None
155 def sendNegotiate(self, negotiateMessage):
156 return self.session.sendNegotiate(negotiateMessage)
158 def sendAuth(self, authenticateMessageBlob, serverChallenge=None):
159 self.sessionData = self.session.sessionData
160 return self.session.sendAuth(authenticateMessageBlob, serverChallenge)