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

17import base64 

18 

19from impacket import LOG 

20from 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: 

24PLUGIN_CLASS = "SMTPSocksRelay" 

25EOL = '\r\n' 

26 

27class 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