Repository: fortra/impacket
Issue category: Security Vulnerability

SOCKS server listens on all interfaces by default
### Configuration  
impacket version:  v0.10.1.dev1+20230207.182628.6cd68a05
Python version:  3.9.2
Target OS:  NA
 
By default, the SOCKS server used by ntlmrelayx.py listens on all interfaces (0.0.0.0)1080, which is dangerous.

Please see:
https://github.com/fortra/impacket/blob/6328a9b74a1df675fb47d8cc3a626bddc1f49b1d/impacket/examples/ntlmrelayx/servers/socksserver.py#L456

Any malicious agent on the same network could take advantage of the listening SOCKS proxy.
This service should only be exposed on localhost by default, and a supplemental flag should be provided in order to make it listen "publicly".


Functions to edit:
examples/ntlmrelayx.py:MiniShell.__init__
examples/ntlmrelayx.py:MiniShell.do_socks
impacket/examples/ntlmrelayx/servers/socksserver.py:webService
impacket/examples/ntlmrelayx/servers/socksserver.py:SOCKS.__init__

Patch:
diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py
index 5377a695e..e3efbbf81 100755
--- a/examples/ntlmrelayx.py
+++ b/examples/ntlmrelayx.py
@@ -57,10 +57,11 @@
 RELAY_SERVERS = []
 
 class MiniShell(cmd.Cmd):
-    def __init__(self, relayConfig, threads):
+    def __init__(self, relayConfig, threads, api_address):
         cmd.Cmd.__init__(self)
 
         self.prompt = 'ntlmrelayx> '
+        self.api_address = api_address
         self.tid = None
         self.relayConfig = relayConfig
         self.intro = 'Type help for list of commands'
@@ -108,7 +109,7 @@ def do_socks(self, line):
         '''
 
         headers = ["Protocol", "Target", "Username", "AdminStatus", "Port"]
-        url = "http://localhost:9090/ntlmrelayx/api/v1.0/relays"
+        url = "http://{}/ntlmrelayx/api/v1.0/relays".format(self.api_address)
         try:
             proxy_handler = ProxyHandler({})
             opener = build_opener(proxy_handler)
@@ -305,6 +306,9 @@ def stop_servers(threads):
                                                                              'SMB Server (16 hex bytes long. eg: 1122334455667788)')
     parser.add_argument('-socks', action='store_true', default=False,
                         help='Launch a SOCKS proxy for the connection relayed')
+    parser.add_argument('-socks-address', default='127.0.0.1', help='SOCKS5 server address (also used for HTTP API)')
+    parser.add_argument('-socks-port', default=1080, type=int, help='SOCKS5 server port')
+    parser.add_argument('-http-api-port', default=9090, type=int, help='SOCKS5 HTTP API port')
     parser.add_argument('-wh','--wpad-host', action='store',help='Enable serving a WPAD file for Proxy Authentication attack, '
                                                                    'setting the proxy host to the one supplied.')
     parser.add_argument('-wa','--wpad-auth-num', action='store', type=int, default=1, help='Prompt for authentication N times for clients without MS16-077 installed '
@@ -471,8 +475,9 @@ def stop_servers(threads):
     threads = set()
     socksServer = None
     if options.socks is True:
+
         # Start a SOCKS proxy in the background
-        socksServer = SOCKS()
+        socksServer = SOCKS(server_address=(options.socks_address, options.socks_port), api_port=options.http_api_port)
         socksServer.daemon_threads = True
         socks_thread = Thread(target=socksServer.serve_forever)
         socks_thread.daemon = True
@@ -485,7 +490,7 @@ def stop_servers(threads):
     logging.info("Servers started, waiting for connections")
     try:
         if options.socks:
-            shell = MiniShell(c, threads)
+            shell = MiniShell(c, threads, api_address='{}:{}'.format(options.socks_address, options.http_api_port))
             shell.cmdloop()
         else:
             sys.stdin.read()
diff --git a/impacket/examples/ntlmrelayx/servers/socksserver.py b/impacket/examples/ntlmrelayx/servers/socksserver.py
index 2fe53989e..616142e54 100644
--- a/impacket/examples/ntlmrelayx/servers/socksserver.py
+++ b/impacket/examples/ntlmrelayx/servers/socksserver.py
@@ -243,36 +243,40 @@ def activeConnectionsWatcher(server):
             LOG.info('Relay connection for %s at %s(%d) already exists. Discarding' % (userName, target, port))
             client.killConnection()
 
-def webService(server):
-    from flask import Flask, jsonify
 
-    app = Flask(__name__)
+def webService(addr, port):
+    def _webService(server):
+        from flask import Flask, jsonify
 
-    log = logging.getLogger('werkzeug')
-    log.setLevel(logging.ERROR)
+        app = Flask(__name__)
 
-    @app.route('/')
-    def index():
-        print(server.activeRelays)
-        return "Relays available: %s!" % (len(server.activeRelays))
+        log = logging.getLogger('werkzeug')
+        log.setLevel(logging.ERROR)
 
-    @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
-    def get_relays():
-        relays = []
-        for target in server.activeRelays:
-            for port in server.activeRelays[target]:
-                for user in server.activeRelays[target][port]:
-                    if user != 'data' and user != 'scheme':
-                        protocol = server.activeRelays[target][port]['scheme']
-                        isAdmin = server.activeRelays[target][port][user]['isAdmin']
-                        relays.append([protocol, target, user, isAdmin, str(port)])
-        return jsonify(relays)
+        @app.route('/')
+        def index():
+            print(server.activeRelays)
+            return "Relays available: %s!" % (len(server.activeRelays))
 
-    @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
-    def get_info(relay):
-        pass
+        @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
+        def get_relays():
+            relays = []
+            for target in server.activeRelays:
+                for port in server.activeRelays[target]:
+                    for user in server.activeRelays[target][port]:
+                        if user != 'data' and user != 'scheme':
+                            protocol = server.activeRelays[target][port]['scheme']
+                            isAdmin = server.activeRelays[target][port][user]['isAdmin']
+                            relays.append([protocol, target, user, isAdmin, str(port)])
+            return jsonify(relays)
 
-    app.run(host='0.0.0.0', port=9090)
+        @app.route('/ntlmrelayx/api/v1.0/relays', methods=['GET'])
+        def get_info(relay):
+            pass
+
+        app.run(host=addr, port=port)
+
+    return _webService
 
 class SocksRequestHandler(socketserver.BaseRequestHandler):
     def __init__(self, request, client_address, server):
@@ -453,8 +457,8 @@ def handle(self):
 
 
 class SOCKS(socketserver.ThreadingMixIn, socketserver.TCPServer):
-    def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestHandler):
-        LOG.info('SOCKS proxy started. Listening at port %d', server_address[1] )
+    def __init__(self, server_address=('127.0.0.1', 1080), handler_class=SocksRequestHandler, api_port=9090):
+        LOG.info('SOCKS proxy started. Listening on %s:%d', server_address[0], server_address[1])
 
         self.activeRelays = {}
         self.socksPlugins = {}
@@ -476,7 +480,7 @@ def __init__(self, server_address=('0.0.0.0', 1080), handler_class=SocksRequestH
         self.__timer = RepeatedTimer(KEEP_ALIVE_TIMER, keepAliveTimer, self)
 
         # Let's start our RESTful API
-        self.restAPI = Thread(target=webService, args=(self, ))
+        self.restAPI = Thread(target=webService(server_address[0], api_port), args=(self, ))
         self.restAPI.daemon = True
         self.restAPI.start()