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