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

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#
17import base64
19from impacket import LOG
20from impacket.examples.ntlmrelayx.servers.socksserver import SocksRelay
22# Besides using this base class you need to define one global variable when
23# writing a plugin:
24PLUGIN_CLASS = "HTTPSocksRelay"
25EOL = b'\r\n'
27class HTTPSocksRelay(SocksRelay):
28 PLUGIN_NAME = 'HTTP Socks Plugin'
29 PLUGIN_SCHEME = 'HTTP'
31 def __init__(self, targetHost, targetPort, socksSocket, activeRelays):
32 SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays)
33 self.packetSize = 8192
35 @staticmethod
36 def getProtocolPort():
37 return 80
39 def initConnection(self):
40 pass
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)
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
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
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
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
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)
132 def transferChunked(self, data, headers):
133 headerSize = data.find(EOL+EOL)
135 self.socksSocket.send(data[:headerSize + 4])
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)
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)
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
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()