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

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

# 

# Author: 

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

# 

# Description: 

# A simple SOCKS server that proxies a connection to relayed HTTP 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 = "HTTPSocksRelay" 

EOL = '\r\n' 

 

class HTTPSocksRelay(SocksRelay): 

PLUGIN_NAME = 'HTTP Socks Plugin' 

PLUGIN_SCHEME = 'HTTP' 

 

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

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

self.packetSize = 8192 

 

@staticmethod 

def getProtocolPort(): 

return 80 

 

def initConnection(self): 

pass 

 

def skipAuthentication(self): 

# See if the user provided authentication 

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

# Get headers from data 

headerDict = self.getHeaders(data) 

try: 

creds = headerDict['authorization'] 

if 'Basic' not in creds: 

raise KeyError() 

basicAuth = base64.b64decode(creds[6:]) 

self.username = basicAuth.split(':')[0].upper() 

if '@' in self.username: 

# Workaround for clients which specify users with the full FQDN 

# such as ruler 

user, domain = self.username.split('@', 1) 

# Currently we only use the first part of the FQDN 

# this might break stuff on tools that do use an FQDN 

# where the domain NETBIOS name is not equal to the part 

# before the first . 

self.username = '%s/%s' % (domain.split('.')[0], user) 

 

# 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('HTTP: Connection for %s@%s(%s) is being used at the moment!' % ( 

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

return False 

else: 

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

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

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

else: 

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

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

return False 

 

except KeyError: 

# User didn't provide authentication yet, prompt for it 

LOG.debug('No authentication provided, prompting for basic authentication') 

reply = ['HTTP/1.1 401 Unauthorized','WWW-Authenticate: Basic realm="ntlmrelayx - provide a DOMAIN/username"','Connection: close','',''] 

self.socksSocket.send(EOL.join(reply)) 

return False 

 

# When we are here, we have a session 

# Point our socket to the sock attribute of HTTPConnection 

# (contained in the session), which contains the socket 

self.relaySocket = self.session.sock 

# Send the initial request to the server 

tosend = self.prepareRequest(data) 

self.relaySocket.send(tosend) 

# Send the response back to the client 

self.transferResponse() 

return True 

 

def getHeaders(self, data): 

# Get the headers from the request, ignore first "header" 

# since this is the HTTP method, identifier, version 

headerSize = data.find(EOL+EOL) 

headers = data[:headerSize].split(EOL)[1:] 

headerDict = {hdrKey.split(':')[0].lower():hdrKey.split(':', 1)[1][1:] for hdrKey in headers} 

return headerDict 

 

def transferResponse(self): 

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

headerSize = data.find(EOL+EOL) 

headers = self.getHeaders(data) 

try: 

bodySize = int(headers['content-length']) 

readSize = len(data) 

# Make sure we send the entire response, but don't keep it in memory 

self.socksSocket.send(data) 

while readSize < bodySize + headerSize + 4: 

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

readSize += len(data) 

self.socksSocket.send(data) 

except KeyError: 

try: 

if headers['transfer-encoding'] == 'chunked': 

# Chunked transfer-encoding, bah 

LOG.debug('Server sent chunked encoding - transferring') 

self.transferChunked(data, headers) 

else: 

# No body in the response, send as-is 

self.socksSocket.send(data) 

except KeyError: 

# No body in the response, send as-is 

self.socksSocket.send(data) 

 

def transferChunked(self, data, headers): 

headerSize = data.find(EOL+EOL) 

 

self.socksSocket.send(data[:headerSize + 4]) 

 

body = data[headerSize + 4:] 

# Size of the chunk 

datasize = int(body[:body.find(EOL)], 16) 

while datasize > 0: 

# Size of the total body 

bodySize = body.find(EOL) + 2 + datasize + 2 

readSize = len(body) 

# Make sure we send the entire response, but don't keep it in memory 

self.socksSocket.send(body) 

while readSize < bodySize: 

maxReadSize = bodySize - readSize 

body = self.relaySocket.recv(min(self.packetSize, maxReadSize)) 

readSize += len(body) 

self.socksSocket.send(body) 

body = self.relaySocket.recv(self.packetSize) 

datasize = int(body[:body.find(EOL)], 16) 

LOG.debug('Last chunk received - exiting chunked transfer') 

self.socksSocket.send(body) 

 

def prepareRequest(self, data): 

# Parse the HTTP data, removing headers that break stuff 

response = [] 

for part in data.split(EOL): 

# This means end of headers, stop parsing here 

if part == '': 

break 

# Remove the Basic authentication header 

if 'authorization' in part.lower(): 

continue 

# Don't close the connection 

if 'connection: close' in part.lower(): 

response.append('Connection: Keep-Alive') 

continue 

# If we are here it means we want to keep the header 

response.append(part) 

# Append the body 

response.append('') 

response.append(data.split(EOL+EOL)[1]) 

senddata = EOL.join(response) 

 

# Check if the body is larger than 1 packet 

headerSize = data.find(EOL+EOL) 

headers = self.getHeaders(data) 

try: 

bodySize = int(headers['content-length']) 

readSize = len(data) 

while readSize < bodySize + headerSize + 4: 

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

readSize += len(data) 

senddata += data 

except KeyError: 

# No body, could be a simple GET or a POST without body 

# no need to check if we already have the full packet 

pass 

return senddata 

 

 

def tunnelConnection(self): 

while True: 

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

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

if data == '': 

return 

# Pass the request to the server 

tosend = self.prepareRequest(data) 

self.relaySocket.send(tosend) 

# Send the response back to the client 

self.transferResponse()