Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/imap.py: 8%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/imap.py : 8%

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

8#

9# Author:

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

11#

12# Description:

13# A simple SOCKS server that proxies a connection to relayed IMAP connections

14#

15# ToDo:

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 = "IMAPSocksRelay"

25 EOL = '\r\n'

26

27 class IMAPSocksRelay(SocksRelay):

28 PLUGIN_NAME = 'IMAP Socks Plugin'

29 PLUGIN_SCHEME = 'IMAP'

30

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

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

33 self.packetSize = 8192

34 self.idleState = False

35 self.shouldClose = True

36

37 @staticmethod

38 def getProtocolPort():

39 return 143

40

41 def getServerCapabilities(self):

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

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

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

45 return self.activeRelays[key]['protocolClient'].session.capabilities

46

47 def initConnection(self):

48 pass

49

50 def skipAuthentication(self):

51 self.socksSocket.sendall('* OK The Microsoft Exchange IMAP4 service is ready.'+EOL)

52

53 # Next should be the client requesting CAPABILITIES

54 tag, cmd = self.recvPacketClient()

55 if cmd.upper() == 'CAPABILITY':

56 clientcapabilities = list(self.getServerCapabilities())

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

58 blacklist = ['AUTH=GSSAPI', 'AUTH=NTLM', 'LOGINDISABLED']

59 for cap in blacklist:

60 if cap in clientcapabilities:

61 clientcapabilities.remove(cap)

62

63 # Offer PLAIN auth for specifying the username

64 if 'AUTH=PLAIN' not in clientcapabilities:

65 clientcapabilities.append('AUTH=PLAIN')

66 # Offer LOGIN for specifying the username

67 if 'LOGIN' not in clientcapabilities:

68 clientcapabilities.append('LOGIN')

69

70 LOG.debug('IMAP: Sending mirrored capabilities from server: %s' % ' '.join(clientcapabilities))

71 self.socksSocket.sendall('* CAPABILITY %s%s%s OK CAPABILITY completed.%s' % (' '.join(clientcapabilities), EOL, tag, EOL))

72 else:

73 LOG.error('IMAP: Socks plugin expected CAPABILITY command, but got: %s' % cmd)

74 return False

75 # next

76 tag, cmd = self.recvPacketClient()

77 args = cmd.split(' ')

78 if cmd.upper() == 'AUTHENTICATE PLAIN':

79 # Send continuation command

80 self.socksSocket.sendall('+'+EOL)

81 # Client will now send their AUTH

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

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

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

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

86 elif args[0].upper() == 'LOGIN':

87 # Simple login

88 self.username = args[1].upper()

89 else:

90 LOG.error('IMAP: Socks plugin expected LOGIN or AUTHENTICATE PLAIN command, but got: %s' % cmd)

91 return False

92

93 # Check if we have a connection for the user

94 if self.username in self.activeRelays:

95 # Check the connection is not inUse

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

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

98 self.username, self.targetHost, self.targetPort))

99 return False

100 else:

101 LOG.info('IMAP: Proxying client session for %s@%s(%s)' % (

102 self.username, self.targetHost, self.targetPort))

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

104 else:

105 LOG.error('IMAP: No session for %s@%s(%s) available' % (

106 self.username, self.targetHost, self.targetPort))

107 return False

108

109 # We arrived here, that means all is OK

110 self.socksSocket.sendall('%s OK %s completed.%s' % (tag, args[0].upper(), EOL))

111 self.relaySocket = self.session.sock

112 self.relaySocketFile = self.session.file

113 return True

114

115 def tunnelConnection(self):

116 keyword = ''

117 tag = ''

118 while True:

119 try:

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

121 except Exception as e:

122 # Socks socket (client) closed connection or something else. Not fatal for killing the existing relay

123 print((keyword, tag))

124 LOG.debug('IMAP: sockSocket recv(): %s' % (str(e)))

125 break

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

127 if data == '':

128 break

129 # Set the new keyword, unless it is false, then break out of the function

130 result = self.processTunnelData(keyword, tag, data)

131

132 if result is False:

133 break

134 # If its not false, it's a tuple with the keyword and tag

135 keyword, tag = result

136

137 if tag != '':

138 # Store the tag in the session so we can continue

139 tag = int(tag)

140 if self.idleState is True:

141 self.relaySocket.sendall('DONE%s' % EOL)

142 self.relaySocketFile.readline()

143

144 if self.shouldClose:

145 tag +=1

146 self.relaySocket.sendall('%s CLOSE%s' % (tag, EOL))

147 self.relaySocketFile.readline()

148

149 self.session.tagnum = tag+1

150

151 return

152

153 def processTunnelData(self, keyword, tag, data):

154 # Pass the request to the server, store the tag unless the last command

155 # was a continuation. In the case of the continuation we still check if

156 # there were commands issued after

157 analyze = data.split(EOL)[:-1]

158 if keyword == '+':

159 # We do send the continuation to the server

160 # but we don't analyze it

161 self.relaySocket.sendall(analyze.pop(0)+EOL)

162 keyword = ''

163

164 for line in analyze:

165 info = line.split(' ')

166 tag = info[0]

167 # See if a LOGOUT command was sent, in which case we want to close

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

169 # also handle APPEND commands

170 try:

171 if info[1].upper() == 'IDLE':

172 self.idleState = True

173 elif info[1].upper() == 'DONE':

174 self.idleState = False

175 elif info[1].upper() == 'CLOSE':

176 self.shouldClose = False

177 elif info[1].upper() == 'LOGOUT':

178 self.socksSocket.sendall('%s OK LOGOUT completed.%s' % (tag, EOL))

179 return False

180 elif info[1].upper() == 'APPEND':

181 LOG.debug('IMAP socks APPEND command detected, forwarding email data')

182 # APPEND command sent, forward all the data, no further commands here

183 self.relaySocket.sendall(data)

184 sent = len(data) - len(line) + len(EOL)

185

186 # https://tools.ietf.org/html/rfc7888

187 literal = info[4][1:-1]

188 if literal[-1] == '+':

189 literalPlus = True

190 totalSize = int(literal[:-1])

191 else:

192 literalPlus = False

193 totalSize = int(literal)

194

195 while sent < totalSize:

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

197 self.relaySocket.sendall(data)

198 sent += len(data)

199 LOG.debug('Forwarded %d bytes' % sent)

200

201 if literalPlus:

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

203 self.relaySocket.sendall(data)

204

205 LOG.debug('IMAP socks APPEND command complete')

206 # break out of the analysis loop

207 break

208 except IndexError:

209 pass

210 self.relaySocket.sendall(line+EOL)

211

212 # Send the response back to the client, until the command is complete

213 # or the server requests more data

214 while keyword != tag and keyword != '+':

215 try:

216 data = self.relaySocketFile.readline()

217 except Exception as e:

218 # This didn't break the connection to the server, don't make it fatal

219 LOG.debug("IMAP relaySocketFile: %s" % str(e))

220 return False

221 keyword = data.split(' ', 2)[0]

222 try:

223 self.socksSocket.sendall(data)

224 except Exception as e:

225 LOG.debug("IMAP socksSocket: %s" % str(e))

226 return False

227

228 # Return the keyword to indicate processing was OK

229 return (keyword, tag)

230

231

232 def recvPacketClient(self):

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

234 space = data.find(' ')

235 return (data[:space], data[space:].strip())