Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/smtp.py: 13%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/smtp.py : 13%

## 93 statements

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# A Socks Proxy for the SMTP Protocol

11#

12# A simple SOCKS server that proxies a connection to relayed SMTP connections

13#

14# Author:

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

16#

17 import base64

18

19 from impacket import LOG

20 from impacket.examples.ntlmrelayx.servers.socksserver import SocksRelay

21

22# Besides using this base class you need to define one global variable when

23# writing a plugin:

24 PLUGIN_CLASS = "SMTPSocksRelay"

25 EOL = '\r\n'

26

27 class SMTPSocksRelay(SocksRelay):

28 PLUGIN_NAME = 'SMTP Socks Plugin'

29 PLUGIN_SCHEME = 'SMTP'

30

31 def __init__(self, targetHost, targetPort, socksSocket, activeRelays):

32 SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays)

33 self.packetSize = 8192

34

35 @staticmethod

36 def getProtocolPort():

37 return 25

38

39 def getServerEhlo(self):

40 for key in list(self.activeRelays.keys()):

41 if key != 'data' and key != 'scheme':

42 if 'protocolClient' in self.activeRelays[key]:

43 return self.activeRelays[key]['protocolClient'].session.ehlo_resp

44

45 def initConnection(self):

46 pass

47

48 def skipAuthentication(self):

49 self.socksSocket.send('220 Microsoft ESMTP MAIL Service ready'+EOL)

50

51 # Next should be the client sending the EHLO command

52 cmd, params = self.recvPacketClient().split(' ',1)

53 if cmd.upper() == 'EHLO':

54 clientcapabilities = self.getServerEhlo().split('\n')

55 # Don't offer these AUTH options so the client won't use them

56 # also don't offer STARTTLS since that will break things

57 blacklist = ['X-EXPS GSSAPI NTLM', 'STARTTLS', 'AUTH NTLM']

58 for cap in blacklist:

59 if cap in clientcapabilities:

60 clientcapabilities.remove(cap)

61

62 # Offer PLAIN auth for specifying the username

63 if 'AUTH PLAIN' not in clientcapabilities:

64 clientcapabilities.append('AUTH PLAIN')

65 # Offer LOGIN for specifying the username

66 if 'AUTH LOGIN' not in clientcapabilities:

67 clientcapabilities.append('AUTH LOGIN')

68

69 LOG.debug('SMTP: Sending mirrored capabilities from server: %s' % ', '.join(clientcapabilities))

70 # Prepare capabilities

71 delim = EOL+'250-'

72 caps = delim.join(clientcapabilities[:-1]) + EOL + '250 ' + clientcapabilities[-1] + EOL

73 self.socksSocket.send('250-%s' % caps)

74 else:

75 LOG.error('SMTP: Socks plugin expected EHLO command, but got: %s %s' % (cmd, params))

76 return False

77 # next

78 cmd, params = self.recvPacketClient().split(' ', 1)

79 args = params.split(' ')

80 if cmd.upper() == 'AUTH' and args[0] == 'LOGIN':

81 # OK, ask for their username

82 self.socksSocket.send('334 VXNlcm5hbWU6'+EOL)

83 # Client will now send their AUTH

84 data = self.socksSocket.recv(self.packetSize)

85 # This contains base64(username), decode

86 creds = base64.b64decode(data.strip())

87 self.username = creds.upper()

88 # Client will now send the password, we don't care for it but receive it anyway

89 self.socksSocket.send('334 UGFzc3dvcmQ6'+EOL)

90 data = self.socksSocket.recv(self.packetSize)

91 elif cmd.upper() == 'AUTH' and args[0] == 'PLAIN':

92 # Simple login

93 # This contains base64(\x00username\x00password), decode and split

94 creds = base64.b64decode(args[1].strip())

95 self.username = creds.split('\x00')[1].upper()

96 else:

97 LOG.error('SMTP: Socks plugin expected AUTH PLAIN or AUTH LOGIN command, but got: %s %s' % (cmd, params))

98 return False

99

100 # Check if we have a connection for the user

101 if self.username in self.activeRelays:

102 # Check the connection is not inUse

103 if self.activeRelays[self.username]['inUse'] is True:

104 LOG.error('SMTP: Connection for %s@%s(%s) is being used at the moment!' % (

105 self.username, self.targetHost, self.targetPort))

106 return False

107 else:

108 LOG.info('SMTP: Proxying client session for %s@%s(%s)' % (

109 self.username, self.targetHost, self.targetPort))

110 self.session = self.activeRelays[self.username]['protocolClient'].session

111 else:

112 LOG.error('SMTP: No session for %s@%s(%s) available' % (

113 self.username, self.targetHost, self.targetPort))

114 return False

115

116 # We arrived here, that means all is OK

117 self.socksSocket.send('235 2.7.0 Authentication successful%s' % EOL)

118 self.relaySocket = self.session.sock

119 self.relaySocketFile = self.session.file

120 return True

121

122 def tunnelConnection(self):

123 doneIndicator = EOL+'.'+EOL

124 while True:

125 data = self.socksSocket.recv(self.packetSize)

126 # If this returns with an empty string, it means the socket was closed

127 if data == '':

128 return

129 info = data.strip().split(' ')

130 # See if a QUIT command was sent, in which case we want to close

131 # the connection to the client but keep the relayed connection alive

132 if info[0].upper() == 'QUIT':

133 LOG.debug('Client sent QUIT command, closing socks connection to client')

134 self.socksSocket.send('221 2.0.0 Service closing transmission channel%s' % EOL)

135 return

136 self.relaySocket.send(data)

137 data = self.relaySocket.recv(self.packetSize)

138 self.socksSocket.send(data)

139 if info[0].upper() == 'DATA':

140 LOG.debug('SMTP Socks entering DATA transfer mode')

141 # DATA transfer, forward to the server till done

142 while data[-5:] != doneIndicator:

143 prevdata = data

144 data = self.socksSocket.recv(self.packetSize)

145 self.relaySocket.send(data)

146 if len(data) < 5:

147 # This can happen, the .CRLF will be in a packet after the first CRLF

148 # we stitch them back together for analysis

149 data = prevdata + data

150 LOG.debug('SMTP Socks DATA transfer mode finished')

151 # DATA done, forward server reply

152 data = self.relaySocket.recv(self.packetSize)

153 self.socksSocket.send(data)

154

155 def recvPacketClient(self):

156 data = self.socksSocket.recv(self.packetSize)

157 return data