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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

# 

# This software is provided under under a slightly modified version 

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

# for more information. 

# 

# A Socks Proxy for the IMAP Protocol 

# 

# Author: 

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

# 

# Description: 

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

# 

# ToDo: 

# 

import base64 

 

from impacket import LOG 

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

 

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

# writing a plugin: 

PLUGIN_CLASS = "IMAPSocksRelay" 

EOL = '\r\n' 

 

class IMAPSocksRelay(SocksRelay): 

PLUGIN_NAME = 'IMAP Socks Plugin' 

PLUGIN_SCHEME = 'IMAP' 

 

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

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

self.packetSize = 8192 

self.idleState = False 

self.shouldClose = True 

 

@staticmethod 

def getProtocolPort(): 

return 143 

 

def getServerCapabilities(self): 

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

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

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

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

 

def initConnection(self): 

pass 

 

def skipAuthentication(self): 

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

 

# Next should be the client requesting CAPABILITIES 

tag, cmd = self.recvPacketClient() 

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

clientcapabilities = list(self.getServerCapabilities()) 

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

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

for cap in blacklist: 

if cap in clientcapabilities: 

clientcapabilities.remove(cap) 

 

# Offer PLAIN auth for specifying the username 

if 'AUTH=PLAIN' not in clientcapabilities: 

clientcapabilities.append('AUTH=PLAIN') 

# Offer LOGIN for specifying the username 

if 'LOGIN' not in clientcapabilities: 

clientcapabilities.append('LOGIN') 

 

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

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

else: 

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

return False 

# next 

tag, cmd = self.recvPacketClient() 

args = cmd.split(' ') 

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

# Send continuation command 

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

# Client will now send their AUTH 

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

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

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

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

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

# Simple login 

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

else: 

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

return False 

 

# Check if we have a connection for the user 

if self.username in self.activeRelays: 

# Check the connection is not inUse 

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

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

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

return False 

else: 

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

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

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

else: 

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

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

return False 

 

# We arrived here, that means all is OK 

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

self.relaySocket = self.session.sock 

self.relaySocketFile = self.session.file 

return True 

 

def tunnelConnection(self): 

keyword = '' 

tag = '' 

while True: 

try: 

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

except Exception as e: 

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

print((keyword, tag)) 

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

break 

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

if data == '': 

break 

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

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

 

if result is False: 

break 

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

keyword, tag = result 

 

if tag != '': 

# Store the tag in the session so we can continue 

tag = int(tag) 

if self.idleState is True: 

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

self.relaySocketFile.readline() 

 

if self.shouldClose: 

tag +=1 

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

self.relaySocketFile.readline() 

 

self.session.tagnum = tag+1 

 

return 

 

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

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

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

# there were commands issued after 

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

if keyword == '+': 

# We do send the continuation to the server 

# but we don't analyze it 

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

keyword = '' 

 

for line in analyze: 

info = line.split(' ') 

tag = info[0] 

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

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

# also handle APPEND commands 

try: 

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

self.idleState = True 

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

self.idleState = False 

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

self.shouldClose = False 

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

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

return False 

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

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

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

self.relaySocket.sendall(data) 

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

 

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

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

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

literalPlus = True 

totalSize = int(literal[:-1]) 

else: 

literalPlus = False 

totalSize = int(literal) 

 

while sent < totalSize: 

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

self.relaySocket.sendall(data) 

sent += len(data) 

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

 

if literalPlus: 

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

self.relaySocket.sendall(data) 

 

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

# break out of the analysis loop 

break 

except IndexError: 

pass 

self.relaySocket.sendall(line+EOL) 

 

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

# or the server requests more data 

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

try: 

data = self.relaySocketFile.readline() 

except Exception as e: 

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

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

return False 

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

try: 

self.socksSocket.sendall(data) 

except Exception as e: 

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

return False 

 

# Return the keyword to indicate processing was OK 

return (keyword, tag) 

 

 

def recvPacketClient(self): 

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

space = data.find(' ') 

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