Hide keyboard shortcuts

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 

29 

30from impacket import LOG 

31from impacket.dcerpc.v5.enum import Enum 

32from 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. 

37KEEP_ALIVE_TIMER = 30.0 

38 

39class enumItems(Enum): 

40 NO_AUTHENTICATION = 0 

41 GSSAPI = 1 

42 USER_PASS = 2 

43 UNACCEPTABLE = 0xFF 

44 

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 

55 

56class ATYP(Enum): 

57 IPv4 = 1 

58 DOMAINNAME = 3 

59 IPv6 = 4 

60 

61class SOCKS5_GREETINGS(Structure): 

62 structure = ( 

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

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

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

66 ) 

67 

68 

69class SOCKS5_GREETINGS_BACK(Structure): 

70 structure = ( 

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

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

73 ) 

74 

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 ) 

83 

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 ) 

92 

93class SOCKS4_REQUEST(Structure): 

94 structure = ( 

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

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

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

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

99 ('PAYLOAD',':'), 

100 ) 

101 

102class SOCKS4_REPLY(Structure): 

103 structure = ( 

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

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

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

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

108 ) 

109 

110activeConnections = 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 

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

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

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

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 

178def 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 

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

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 

243def 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 

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) 

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 

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

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 

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