Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/http.py: 12%

# Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/http.py : 12%

## 123 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 HTTP 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 HTTP 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 = "HTTPSocksRelay"

25 EOL = b'\r\n'

26

27 class HTTPSocksRelay(SocksRelay):

28 PLUGIN_NAME = 'HTTP Socks Plugin'

29 PLUGIN_SCHEME = 'HTTP'

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 80

38

39 def initConnection(self):

40 pass

41

42 def skipAuthentication(self):

43 # See if the user provided authentication

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

45 # Get headers from data

46 headerDict = self.getHeaders(data)

47 try:

48 creds = headerDict['authorization']

49 if 'Basic' not in creds:

50 raise KeyError()

51 basicAuth = base64.b64decode(creds[6:]).decode("ascii")

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

53 if '@' in self.username:

54 # Workaround for clients which specify users with the full FQDN

55 # such as ruler

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

57 # Currently we only use the first part of the FQDN

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

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

60 # before the first .

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

62

63 # Check if we have a connection for the user

64 if self.username in self.activeRelays:

65 # Check the connection is not inUse

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

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

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

69 return False

70 else:

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

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

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

74 else:

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

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

77 return False

78

79 except KeyError:

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

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

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

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

84 return False

85

86 # When we are here, we have a session

87 # Point our socket to the sock attribute of HTTPConnection

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

89 self.relaySocket = self.session.sock

90 # Send the initial request to the server

91 tosend = self.prepareRequest(data)

92 self.relaySocket.send(tosend)

93 # Send the response back to the client

94 self.transferResponse()

95 return True

96

97 def getHeaders(self, data):

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

99 # since this is the HTTP method, identifier, version

100 headerSize = data.find(EOL+EOL)

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

102 headers = [header.decode("ascii") for header in headers]

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

104 return headerDict

105

106 def transferResponse(self):

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

108 headerSize = data.find(EOL+EOL)

109 headers = self.getHeaders(data)

110 try:

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

112 readSize = len(data)

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

114 self.socksSocket.send(data)

115 while readSize < bodySize + headerSize + 4:

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

117 readSize += len(data)

118 self.socksSocket.send(data)

119 except KeyError:

120 try:

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

122 # Chunked transfer-encoding, bah

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

124 self.transferChunked(data, headers)

125 else:

126 # No body in the response, send as-is

127 self.socksSocket.send(data)

128 except KeyError:

129 # No body in the response, send as-is

130 self.socksSocket.send(data)

131

132 def transferChunked(self, data, headers):

133 headerSize = data.find(EOL+EOL)

134

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

136

137 body = data[headerSize + 4:]

138 # Size of the chunk

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

140 while datasize > 0:

141 # Size of the total body

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

143 readSize = len(body)

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

145 self.socksSocket.send(body)

146 while readSize < bodySize:

147 maxReadSize = bodySize - readSize

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

149 readSize += len(body)

150 self.socksSocket.send(body)

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

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

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

154 self.socksSocket.send(body)

155

156 def prepareRequest(self, data):

157 # Parse the HTTP data, removing headers that break stuff

158 response = []

159 for part in data.split(EOL):

160 # This means end of headers, stop parsing here

161 if part == '':

162 break

163 # Remove the Basic authentication header

164 if b'authorization' in part.lower():

165 continue

166 # Don't close the connection

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

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

169 continue

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

171 response.append(part)

172 # Append the body

173 response.append(b'')

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

175 senddata = EOL.join(response)

176

177 # Check if the body is larger than 1 packet

178 headerSize = data.find(EOL+EOL)

179 headers = self.getHeaders(data)

180 try:

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

182 readSize = len(data)

183 while readSize < bodySize + headerSize + 4:

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

185 readSize += len(data)

186 senddata += data

187 except KeyError:

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

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

190 pass

191 return senddata

192

193

194 def tunnelConnection(self):

195 while True:

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

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

198 if data == '':

199 return

200 # Pass the request to the server

201 tosend = self.prepareRequest(data)

202 self.relaySocket.send(tosend)

203 # Send the response back to the client

204 self.transferResponse()