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

20 from __future__ import division

21 from __future__ import print_function

22 import socketserver

23 import socket

24 import time

25 import logging

26 from queue import Queue

27 from struct import unpack, pack

28 from threading import Timer, Thread

29

30 from impacket import LOG

31 from impacket.dcerpc.v5.enum import Enum

32 from impacket.structure import Structure

33

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.

37 KEEP_ALIVE_TIMER = 30.0

38

39 class enumItems(Enum):

40 NO_AUTHENTICATION = 0

41 GSSAPI = 1

42 USER_PASS = 2

43 UNACCEPTABLE = 0xFF

44

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

55

56 class ATYP(Enum):

57 IPv4 = 1

58 DOMAINNAME = 3

59 IPv6 = 4

60

61 class SOCKS5_GREETINGS(Structure):

62 structure = (

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

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

65 ('METHODS','B*B'),

66 )

67

68

69 class SOCKS5_GREETINGS_BACK(Structure):

70 structure = (

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

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

73 )

74

75 class SOCKS5_REQUEST(Structure):

76 structure = (

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

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

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

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

81 ('PAYLOAD',':'),

82 )

83

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

92

93 class SOCKS4_REQUEST(Structure):

94 structure = (

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

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

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

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

99 ('PAYLOAD',':'),

100 )

101

102 class SOCKS4_REPLY(Structure):

103 structure = (

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

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

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

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

108 )

109

110 activeConnections = Queue()

111

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

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

124

125 def _run(self):

126 self.is_running = False

127 self.start()

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

129

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

136

137 def stop(self):

138 self._timer.cancel()

139 self.is_running = False

140

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

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

150

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

159

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

163

164 def skipAuthentication(self):

165 # Charged of bypassing any authentication attempt from the client

166 raise RuntimeError('Virtual Function')

167

168 def tunnelConnection(self):

169 # Charged of tunneling the rest of the connection

170 raise RuntimeError('Virtual Function')

171

172 @staticmethod

173 def getProtocolPort(self):

174 # Should return the port this relay works against

175 raise RuntimeError('Virtual Function')

176

177

178 def keepAliveTimer(server):

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

180

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

203

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

213

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

226

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

242

243 def webService(server):

244 from flask import Flask, jsonify

245

246 app = Flask(__name__)

247

248 log = logging.getLogger('werkzeug')

249 log.setLevel(logging.ERROR)

250

251 @app.route('/')

252 def index():

253 print(server.activeRelays)

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

255

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)

267

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

269 def get_info(relay):

270 pass

271

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

273

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

284

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

286

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

295

296 def handle(self):

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

298

299 data = self.__connSocket.recv(8192)

300 grettings = SOCKS5_GREETINGS_BACK(data)

301 self.__socksVersion = grettings['VER']

302

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)

311

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

327

328 # SOCKS4a

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

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

331

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

338

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

340

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

353

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

366

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

374

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

376

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

388

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

394

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

399

400 try:

401 relay.initConnection()

402

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

411

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

413

414 if relay.skipAuthentication() is not True:

415 # Something didn't go right

416 # Close the socket

417 self.__connSocket.close()

418 return

419

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

423

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

438

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

444

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

450

451

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

455

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)

463

464 # Let's register the socksplugins plugins we have

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

466

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)

471

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

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

474

475 # Let's start our RESTful API

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

477 self.restAPI.daemon = True

478 self.restAPI.start()

479

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

484

485 def shutdown(self):

486 self.__timer.stop()

487 del self.restAPI

488 del self.activeConnectionsWatcher

489 return socketserver.TCPServer.shutdown(self)

490

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