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

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

## 309 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#!/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#

23 from __future__ import division

24 from __future__ import print_function

25 import socketserver

26 import socket

27 import time

28 import logging

29 from queue import Queue

30 from struct import unpack, pack

31 from threading import Timer, Thread

32

33 from impacket import LOG

34 from impacket.dcerpc.v5.enum import Enum

35 from impacket.structure import Structure

36

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.

40 KEEP_ALIVE_TIMER = 30.0

41

42 class enumItems(Enum):

43 NO_AUTHENTICATION = 0

44 GSSAPI = 1

45 USER_PASS = 2

46 UNACCEPTABLE = 0xFF

47

48 class 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

58

59 class ATYP(Enum):

60 IPv4 = 1

61 DOMAINNAME = 3

62 IPv6 = 4

63

64 class SOCKS5_GREETINGS(Structure):

65 structure = (

66 ('VER','B=5'),

67 #('NMETHODS','B=0'),

68 ('METHODS','B*B'),

69 )

70

71

72 class SOCKS5_GREETINGS_BACK(Structure):

73 structure = (

74 ('VER','B=5'),

75 ('METHODS','B=0'),

76 )

77

78 class SOCKS5_REQUEST(Structure):

79 structure = (

80 ('VER','B=5'),

81 ('CMD','B=0'),

82 ('RSV','B=0'),

83 ('ATYP','B=0'),

84 ('PAYLOAD',':'),

85 )

86

87 class 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 )

95

96 class SOCKS4_REQUEST(Structure):

97 structure = (

98 ('VER','B=4'),

99 ('CMD','B=0'),

100 ('PORT','>H=0'),

101 ('ADDR','4s="'),

102 ('PAYLOAD',':'),

103 )

104

105 class SOCKS4_REPLY(Structure):

106 structure = (

107 ('VER','B=0'),

108 ('REP','B=0x5A'),

109 ('RSV','<H=0'),

110 ('RSV','<L=0'),

111 )

112

113 activeConnections = Queue()

114

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

117 class 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()

127

128 def _run(self):

129 self.is_running = False

130 self.start()

131 self.function(*self.args, **self.kwargs)

132

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

139

140 def stop(self):

141 self._timer.cancel()

142 self.is_running = False

143

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>"

148 class 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 = ''

153

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

162

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')

166

167 def skipAuthentication(self):

168 # Charged of bypassing any authentication attempt from the client

169 raise RuntimeError('Virtual Function')

170

171 def tunnelConnection(self):

172 # Charged of tunneling the rest of the connection

173 raise RuntimeError('Virtual Function')

174

175 @staticmethod

176 def getProtocolPort(self):

177 # Should return the port this relay works against

178 raise RuntimeError('Virtual Function')

179

180

181 def keepAliveTimer(server):

182 LOG.debug('KeepAlive Timer reached. Updating connections')

183

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))

206

207 def 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] = {}

216

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

229

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()

245

246 def webService(server):

247 from flask import Flask, jsonify

248

249 app = Flask(__name__)

250

251 log = logging.getLogger('werkzeug')

252 log.setLevel(logging.ERROR)

253

254 @app.route('/')

255 def index():

256 print(server.activeRelays)

257 return "Relays available: %s!" % (len(server.activeRelays))

258

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)

270

271 @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])

272 def get_info(relay):

273 pass

274

275 app.run(host='0.0.0.0', port=9090)

276

277 class 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)

287

288 def sendReplyError(self, error = replyField.CONNECTION_REFUSED):

289

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())

298

299 def handle(self):

300 LOG.debug("SOCKS: New Connection from %s(%s)" % (self.__ip, self.__port))

301

302 data = self.__connSocket.recv(8192)

303 grettings = SOCKS5_GREETINGS_BACK(data)

304 self.__socksVersion = grettings['VER']

305

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)

314

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']

330

331 # SOCKS4a

332 if request['ADDR'][:3] == "\x00\x00\x00" and request['ADDR'][3] != "\x00":

333 nullBytePos = request['PAYLOAD'].find("\x00")

334

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'])

341

342 LOG.debug('SOCKS: Target is %s(%s)' % (self.targetHost, self.targetPort))

343

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

356

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

369

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()

377

378 self.__connSocket.sendall(reply.getData())

379

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))

391

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']

397

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])

402

403 try:

404 relay.initConnection()

405

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()

414

415 self.__connSocket.sendall(reply.getData())

416

417 if relay.skipAuthentication() is not True:

418 # Something didn't go right

419 # Close the socket

420 self.__connSocket.close()

421 return

422

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

426

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

441

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')

447

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))

453

454

455 class 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] )

458

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)

466

467 # Let's register the socksplugins plugins we have

468 from impacket.examples.ntlmrelayx.servers.socksplugins import SOCKS_RELAYS

469

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)

474

475 # Let's create a timer to keep the connections up.

476 self.__timer = RepeatedTimer(KEEP_ALIVE_TIMER, keepAliveTimer, self)

477

478 # Let's start our RESTful API

479 self.restAPI = Thread(target=webService, args=(self, ))

480 self.restAPI.daemon = True

481 self.restAPI.start()

482

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()

487

488 def shutdown(self):

489 self.__timer.stop()

490 del self.restAPI

491 del self.activeConnectionsWatcher

492 return socketserver.TCPServer.shutdown(self)

493

494 if __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()