Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksserver.py : 18%

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#!/usr/bin/env python
2# Impacket - Collection of Python classes for working with network protocols.
3#
4# SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved.
5#
6# This software is provided under a slightly modified version
7# of the Apache Software License. See the accompanying LICENSE file
8# for more information.
9#
10# Description:
11# SOCKS proxy server/client
12#
13# A simple SOCKS server that proxy connection to relayed connections
14#
15# Author:
16# Alberto Solino (@agsolino)
17#
18# ToDo:
19# [ ] Handle better the SOCKS specification (RFC1928), e.g. BIND
20# [ ] Port handlers should be dynamically subscribed, and coded in another place. This will help coding
21# proxies for different protocols (e.g. MSSQL)
22#
23from __future__ import division
24from __future__ import print_function
25import socketserver
26import socket
27import time
28import logging
29from queue import Queue
30from struct import unpack, pack
31from threading import Timer, Thread
33from impacket import LOG
34from impacket.dcerpc.v5.enum import Enum
35from impacket.structure import Structure
37# Amount of seconds each socks plugin keep alive function will be called
38# It is up to each plugin to send the keep alive to the target or not in every hit.
39# In some cases (e.g. SMB) it is not needed to send a keep alive every 30 secs.
40KEEP_ALIVE_TIMER = 30.0
42class enumItems(Enum):
43 NO_AUTHENTICATION = 0
44 GSSAPI = 1
45 USER_PASS = 2
46 UNACCEPTABLE = 0xFF
48class replyField(Enum):
49 SUCCEEDED = 0
50 SOCKS_FAILURE = 1
51 NOT_ALLOWED = 2
52 NETWORK_UNREACHABLE = 3
53 HOST_UNREACHABLE = 4
54 CONNECTION_REFUSED = 5
55 TTL_EXPIRED = 6
56 COMMAND_NOT_SUPPORTED = 7
57 ADDRESS_NOT_SUPPORTED = 8
59class ATYP(Enum):
60 IPv4 = 1
61 DOMAINNAME = 3
62 IPv6 = 4
64class SOCKS5_GREETINGS(Structure):
65 structure = (
66 ('VER','B=5'),
67 #('NMETHODS','B=0'),
68 ('METHODS','B*B'),
69 )
72class SOCKS5_GREETINGS_BACK(Structure):
73 structure = (
74 ('VER','B=5'),
75 ('METHODS','B=0'),
76 )
78class SOCKS5_REQUEST(Structure):
79 structure = (
80 ('VER','B=5'),
81 ('CMD','B=0'),
82 ('RSV','B=0'),
83 ('ATYP','B=0'),
84 ('PAYLOAD',':'),
85 )
87class SOCKS5_REPLY(Structure):
88 structure = (
89 ('VER','B=5'),
90 ('REP','B=5'),
91 ('RSV','B=0'),
92 ('ATYP','B=1'),
93 ('PAYLOAD',':="AAAAA"'),
94 )
96class SOCKS4_REQUEST(Structure):
97 structure = (
98 ('VER','B=4'),
99 ('CMD','B=0'),
100 ('PORT','>H=0'),
101 ('ADDR','4s="'),
102 ('PAYLOAD',':'),
103 )
105class SOCKS4_REPLY(Structure):
106 structure = (
107 ('VER','B=0'),
108 ('REP','B=0x5A'),
109 ('RSV','<H=0'),
110 ('RSV','<L=0'),
111 )
113activeConnections = Queue()
115# Taken from https://stackoverflow.com/questions/474528/what-is-the-best-way-to-repeatedly-execute-a-function-every-x-seconds-in-python
116# Thanks https://stackoverflow.com/users/624066/mestrelion
117class RepeatedTimer(object):
118 def __init__(self, interval, function, *args, **kwargs):
119 self._timer = None
120 self.interval = interval
121 self.function = function
122 self.args = args
123 self.kwargs = kwargs
124 self.is_running = False
125 self.next_call = time.time()
126 self.start()
128 def _run(self):
129 self.is_running = False
130 self.start()
131 self.function(*self.args, **self.kwargs)
133 def start(self):
134 if not self.is_running:
135 self.next_call += self.interval
136 self._timer = Timer(self.next_call - time.time(), self._run)
137 self._timer.start()
138 self.is_running = True
140 def stop(self):
141 self._timer.cancel()
142 self.is_running = False
144# Base class for Relay Socks Servers for different protocols (SMB, MSSQL, etc)
145# Besides using this base class you need to define one global variable when
146# writing a plugin for socksplugins:
147# PLUGIN_CLASS = "<name of the class for the plugin>"
148class SocksRelay:
149 PLUGIN_NAME = 'Base Plugin'
150 # The plugin scheme, for automatic registration with relay servers
151 # Should be specified in full caps, e.g. LDAP, HTTPS
152 PLUGIN_SCHEME = ''
154 def __init__(self, targetHost, targetPort, socksSocket, activeRelays):
155 self.targetHost = targetHost
156 self.targetPort = targetPort
157 self.socksSocket = socksSocket
158 self.sessionData = activeRelays['data']
159 self.username = None
160 self.clientConnection = None
161 self.activeRelays = activeRelays
163 def initConnection(self):
164 # Here we do whatever is necessary to leave the relay ready for processing incoming connections
165 raise RuntimeError('Virtual Function')
167 def skipAuthentication(self):
168 # Charged of bypassing any authentication attempt from the client
169 raise RuntimeError('Virtual Function')
171 def tunnelConnection(self):
172 # Charged of tunneling the rest of the connection
173 raise RuntimeError('Virtual Function')
175 @staticmethod
176 def getProtocolPort(self):
177 # Should return the port this relay works against
178 raise RuntimeError('Virtual Function')
181def keepAliveTimer(server):
182 LOG.debug('KeepAlive Timer reached. Updating connections')
184 for target in list(server.activeRelays.keys()):
185 for port in list(server.activeRelays[target].keys()):
186 # Now cycle through the users
187 for user in list(server.activeRelays[target][port].keys()):
188 if user != 'data' and user != 'scheme':
189 # Let's call the keepAlive method for the handler to keep the connection alive
190 if server.activeRelays[target][port][user]['inUse'] is False:
191 LOG.debug('Calling keepAlive() for %s@%s:%s' % (user, target, port))
192 try:
193 server.activeRelays[target][port][user]['protocolClient'].keepAlive()
194 except Exception as e:
195 LOG.debug("Exception:",exc_info=True)
196 LOG.debug('SOCKS: %s' % str(e))
197 if str(e).find('Broken pipe') >= 0 or str(e).find('reset by peer') >=0 or \
198 str(e).find('Invalid argument') >= 0 or str(e).find('Server not connected') >=0:
199 # Connection died, taking out of the active list
200 del (server.activeRelays[target][port][user])
201 if len(list(server.activeRelays[target][port].keys())) == 1:
202 del (server.activeRelays[target][port])
203 LOG.debug('Removing active relay for %s@%s:%s' % (user, target, port))
204 else:
205 LOG.debug('Skipping %s@%s:%s since it\'s being used at the moment' % (user, target, port))
207def activeConnectionsWatcher(server):
208 while True:
209 # This call blocks until there is data, so it doesn't loop endlessly
210 target, port, scheme, userName, client, data = activeConnections.get()
211 # ToDo: Careful. Dicts are not thread safe right?
212 if (target in server.activeRelays) is not True:
213 server.activeRelays[target] = {}
214 if (port in server.activeRelays[target]) is not True:
215 server.activeRelays[target][port] = {}
217 if (userName in server.activeRelays[target][port]) is not True:
218 LOG.info('SOCKS: Adding %s@%s(%s) to active SOCKS connection. Enjoy' % (userName, target, port))
219 server.activeRelays[target][port][userName] = {}
220 # This is the protocolClient. Needed because we need to access the killConnection from time to time.
221 # Inside this instance, you have the session attribute pointing to the relayed session.
222 server.activeRelays[target][port][userName]['protocolClient'] = client
223 server.activeRelays[target][port][userName]['inUse'] = False
224 server.activeRelays[target][port][userName]['data'] = data
225 # Just for the CHALLENGE data, we're storing this general
226 server.activeRelays[target][port]['data'] = data
227 # Let's store the protocol scheme, needed be used later when trying to find the right socks relay server to use
228 server.activeRelays[target][port]['scheme'] = scheme
230 # Default values in case somebody asks while we're getting the data
231 server.activeRelays[target][port][userName]['isAdmin'] = 'N/A'
232 # Do we have admin access in this connection?
233 try:
234 LOG.debug("Checking admin status for user %s" % str(userName))
235 isAdmin = client.isAdmin()
236 server.activeRelays[target][port][userName]['isAdmin'] = isAdmin
237 except Exception as e:
238 # Method not implemented
239 server.activeRelays[target][port][userName]['isAdmin'] = 'N/A'
240 pass
241 LOG.debug("isAdmin returned: %s" % server.activeRelays[target][port][userName]['isAdmin'])
242 else:
243 LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port))
244 client.killConnection()
246def webService(server):
247 from flask import Flask, jsonify
249 app = Flask(__name__)
251 log = logging.getLogger('werkzeug')
252 log.setLevel(logging.ERROR)
254 @app.route('/')
255 def index():
256 print(server.activeRelays)
257 return "Relays available: %s!" % (len(server.activeRelays))
259 @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
260 def get_relays():
261 relays = []
262 for target in server.activeRelays:
263 for port in server.activeRelays[target]:
264 for user in server.activeRelays[target][port]:
265 if user != 'data' and user != 'scheme':
266 protocol = server.activeRelays[target][port]['scheme']
267 isAdmin = server.activeRelays[target][port][user]['isAdmin']
268 relays.append([protocol, target, user, isAdmin, str(port)])
269 return jsonify(relays)
271 @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
272 def get_info(relay):
273 pass
275 app.run(host='0.0.0.0', port=9090)
277class SocksRequestHandler(socketserver.BaseRequestHandler):
278 def __init__(self, request, client_address, server):
279 self.__socksServer = server
280 self.__ip, self.__port = client_address
281 self.__connSocket= request
282 self.__socksVersion = 5
283 self.targetHost = None
284 self.targetPort = None
285 self.__NBSession= None
286 socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
288 def sendReplyError(self, error = replyField.CONNECTION_REFUSED):
290 if self.__socksVersion == 5:
291 reply = SOCKS5_REPLY()
292 reply['REP'] = error.value
293 else:
294 reply = SOCKS4_REPLY()
295 if error.value != 0:
296 reply['REP'] = 0x5B
297 return self.__connSocket.sendall(reply.getData())
299 def handle(self):
300 LOG.debug("SOCKS: New Connection from %s(%s)" % (self.__ip, self.__port))
302 data = self.__connSocket.recv(8192)
303 grettings = SOCKS5_GREETINGS_BACK(data)
304 self.__socksVersion = grettings['VER']
306 if self.__socksVersion == 5:
307 # We need to answer back with a no authentication response. We're not dealing with auth for now
308 self.__connSocket.sendall(SOCKS5_GREETINGS_BACK().getData())
309 data = self.__connSocket.recv(8192)
310 request = SOCKS5_REQUEST(data)
311 else:
312 # We're in version 4, we just received the request
313 request = SOCKS4_REQUEST(data)
315 # Let's process the request to extract the target to connect.
316 # SOCKS5
317 if self.__socksVersion == 5:
318 if request['ATYP'] == ATYP.IPv4.value:
319 self.targetHost = socket.inet_ntoa(request['PAYLOAD'][:4])
320 self.targetPort = unpack('>H',request['PAYLOAD'][4:])[0]
321 elif request['ATYP'] == ATYP.DOMAINNAME.value:
322 hostLength = unpack('!B',request['PAYLOAD'][0])[0]
323 self.targetHost = request['PAYLOAD'][1:hostLength+1]
324 self.targetPort = unpack('>H',request['PAYLOAD'][hostLength+1:])[0]
325 else:
326 LOG.error('No support for IPv6 yet!')
327 # SOCKS4
328 else:
329 self.targetPort = request['PORT']
331 # SOCKS4a
332 if request['ADDR'][:3] == "\x00\x00\x00" and request['ADDR'][3] != "\x00":
333 nullBytePos = request['PAYLOAD'].find("\x00")
335 if nullBytePos == -1:
336 LOG.error('Error while reading SOCKS4a header!')
337 else:
338 self.targetHost = request['PAYLOAD'].split('\0', 1)[1][:-1]
339 else:
340 self.targetHost = socket.inet_ntoa(request['ADDR'])
342 LOG.debug('SOCKS: Target is %s(%s)' % (self.targetHost, self.targetPort))
344 if self.targetPort != 53:
345 # Do we have an active connection for the target host/port asked?
346 # Still don't know the username, but it's a start
347 if self.targetHost in self.__socksServer.activeRelays:
348 if (self.targetPort in self.__socksServer.activeRelays[self.targetHost]) is not True:
349 LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort))
350 self.sendReplyError(replyField.CONNECTION_REFUSED)
351 return
352 else:
353 LOG.error('SOCKS: Don\'t have a relay for %s(%s)' % (self.targetHost, self.targetPort))
354 self.sendReplyError(replyField.CONNECTION_REFUSED)
355 return
357 # Now let's get into the loops
358 if self.targetPort == 53:
359 # Somebody wanting a DNS request. Should we handle this?
360 s = socket.socket()
361 try:
362 LOG.debug('SOCKS: Connecting to %s(%s)' %(self.targetHost, self.targetPort))
363 s.connect((self.targetHost, self.targetPort))
364 except Exception as e:
365 LOG.debug("Exception:", exc_info=True)
366 LOG.error('SOCKS: %s' %str(e))
367 self.sendReplyError(replyField.CONNECTION_REFUSED)
368 return
370 if self.__socksVersion == 5:
371 reply = SOCKS5_REPLY()
372 reply['REP'] = replyField.SUCCEEDED.value
373 addr, port = s.getsockname()
374 reply['PAYLOAD'] = socket.inet_aton(addr) + pack('>H', port)
375 else:
376 reply = SOCKS4_REPLY()
378 self.__connSocket.sendall(reply.getData())
380 while True:
381 try:
382 data = self.__connSocket.recv(8192)
383 if data == b'':
384 break
385 s.sendall(data)
386 data = s.recv(8192)
387 self.__connSocket.sendall(data)
388 except Exception as e:
389 LOG.debug("Exception:", exc_info=True)
390 LOG.error('SOCKS: %s', str(e))
392 # Let's look if there's a relayed connection for our host/port
393 scheme = None
394 if self.targetHost in self.__socksServer.activeRelays:
395 if self.targetPort in self.__socksServer.activeRelays[self.targetHost]:
396 scheme = self.__socksServer.activeRelays[self.targetHost][self.targetPort]['scheme']
398 if scheme is not None:
399 LOG.debug('Handler for port %s found %s' % (self.targetPort, self.__socksServer.socksPlugins[scheme]))
400 relay = self.__socksServer.socksPlugins[scheme](self.targetHost, self.targetPort, self.__connSocket,
401 self.__socksServer.activeRelays[self.targetHost][self.targetPort])
403 try:
404 relay.initConnection()
406 # Let's answer back saying we've got the connection. Data is fake
407 if self.__socksVersion == 5:
408 reply = SOCKS5_REPLY()
409 reply['REP'] = replyField.SUCCEEDED.value
410 addr, port = self.__connSocket.getsockname()
411 reply['PAYLOAD'] = socket.inet_aton(addr) + pack('>H', port)
412 else:
413 reply = SOCKS4_REPLY()
415 self.__connSocket.sendall(reply.getData())
417 if relay.skipAuthentication() is not True:
418 # Something didn't go right
419 # Close the socket
420 self.__connSocket.close()
421 return
423 # Ok, so we have a valid connection to play with. Let's lock it while we use it so the Timer doesn't send a
424 # keep alive to this one.
425 self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username]['inUse'] = True
427 relay.tunnelConnection()
428 except Exception as e:
429 LOG.debug("Exception:", exc_info=True)
430 LOG.debug('SOCKS: %s' % str(e))
431 if str(e).find('Broken pipe') >= 0 or str(e).find('reset by peer') >=0 or \
432 str(e).find('Invalid argument') >= 0:
433 # Connection died, taking out of the active list
434 del(self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username])
435 if len(list(self.__socksServer.activeRelays[self.targetHost][self.targetPort].keys())) == 1:
436 del(self.__socksServer.activeRelays[self.targetHost][self.targetPort])
437 LOG.debug('Removing active relay for %s@%s:%s' % (relay.username, self.targetHost, self.targetPort))
438 self.sendReplyError(replyField.CONNECTION_REFUSED)
439 return
440 pass
442 # Freeing up this connection
443 if relay.username is not None:
444 self.__socksServer.activeRelays[self.targetHost][self.targetPort][relay.username]['inUse'] = False
445 else:
446 LOG.error('SOCKS: I don\'t have a handler for this port')
448 LOG.debug('SOCKS: Shutting down connection')
449 try:
450 self.sendReplyError(replyField.CONNECTION_REFUSED)
451 except Exception as e:
452 LOG.debug('SOCKS END: %s' % str(e))
455class SOCKS(socketserver.ThreadingMixIn, socketserver.TCPServer):
456 def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestHandler):
457 LOG.info('SOCKS proxy started. Listening at port %d', server_address[1] )
459 self.activeRelays = {}
460 self.socksPlugins = {}
461 self.restAPI = None
462 self.activeConnectionsWatcher = None
463 self.supportedSchemes = []
464 socketserver.TCPServer.allow_reuse_address = True
465 socketserver.TCPServer.__init__(self, server_address, handler_class)
467 # Let's register the socksplugins plugins we have
468 from impacket.examples.ntlmrelayx.servers.socksplugins import SOCKS_RELAYS
470 for relay in SOCKS_RELAYS:
471 LOG.info('%s loaded..' % relay.PLUGIN_NAME)
472 self.socksPlugins[relay.PLUGIN_SCHEME] = relay
473 self.supportedSchemes.append(relay.PLUGIN_SCHEME)
475 # Let's create a timer to keep the connections up.
476 self.__timer = RepeatedTimer(KEEP_ALIVE_TIMER, keepAliveTimer, self)
478 # Let's start our RESTful API
479 self.restAPI = Thread(target=webService, args=(self, ))
480 self.restAPI.daemon = True
481 self.restAPI.start()
483 # Let's start out worker for active connections
484 self.activeConnectionsWatcher = Thread(target=activeConnectionsWatcher, args=(self, ))
485 self.activeConnectionsWatcher.daemon = True
486 self.activeConnectionsWatcher.start()
488 def shutdown(self):
489 self.__timer.stop()
490 del self.restAPI
491 del self.activeConnectionsWatcher
492 return socketserver.TCPServer.shutdown(self)
494if __name__ == '__main__': 494 ↛ 495line 494 didn't jump to line 495, because the condition on line 494 was never true
495 from impacket.examples import logger
496 logger.init()
497 s = SOCKS()
498 s.serve_forever()