Empire icon indicating copy to clipboard operation
Empire copied to clipboard

HTTP listeners can have different ports on Host and Port

Open trou opened this issue 6 years ago • 2 comments

Running git HEAD.

When configuring the http listener, the handling of the Host option is confusing:

(Empire: listeners) > uselistener http
(Empire: listeners/http) > set Host https://example.com
(Empire: listeners/http) > info
[...]
  Host              True        https://example.com:80           Hostname/IP for staging.

the code responsible is in lib/common/listeners.py:

                # parse and auto-set some host parameters
                if option == 'Host':

                    if not value.startswith('http'):
                        parts = value.split(':')
                        # if there's a current ssl cert path set, assume this is https
                        if ('CertPath' in listenerObject.options) and (listenerObject.options['CertPath']['Value'] != ''):
                            protocol = 'https'
                            defaultPort = 443
                        else:
                            protocol = 'http'
                            defaultPort = 80

                    elif value.startswith('https'):
                        value = value.split('//')[1]
                        parts = value.split(':')
                        protocol = 'https'
                        defaultPort = 443

                    elif value.startswith('http'):
                        value = value.split('//')[1]
                        parts = value.split(':')
                        protocol = 'http'
                        defaultPort = 80

                    if len(parts) != 1 and parts[-1].isdigit():
                        # if a port is specified with http://host:port
                        listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
                        if listenerObject.options['Port']['Value'] == '':
                            listenerObject.options['Port']['Value'] = parts[-1]
                    elif listenerObject.options['Port']['Value'] != '':
                        # otherwise, check if the port value was manually set
                        listenerObject.options['Host']['Value'] = "%s://%s:%s" % (protocol, value, listenerObject.options['Port']['Value'])
                    else:
                        # otherwise use default port
                        listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
                        if listenerObject.options['Port']['Value'] == '':
                            listenerObject.options['Port']['Value'] = defaultPort

                    return True

this is annoying as the Host value can be completely unrelated to the Port value. I think the code is a bit too clever.

I replaced it with

                # parse and auto-set some host parameters
                if option == 'Host':

                    if value[-1] == '/':
                        value = value.rstrip('/')
                        print helpers.color('[!] Warning: Host should not end with a /, removed.')

                    if not value.startswith('http'):
                        # if there's a current ssl cert path set, assume this is https
                        if ('CertPath' in listenerObject.options) and (listenerObject.options['CertPath']['Value'] != ''):
                            protocol = 'https'
                        else:
                            protocol = 'http'
                        listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
                    else:
                        listenerObject.options['Host']['Value'] = value

                    return True

                elif option == 'CertPath':
                    listenerObject.options[option]['Value'] = value
                    host = listenerObject.options['Host']['Value']
                    # if we're setting a SSL cert path, but the host is specific at http
                    if host.startswith('http:'):
                        print helpers.color('[!] Warning: CertPath is specified, but Host is http, make sure this is right.')
                    return True

                if option == 'Port':
                    listenerObject.options[option]['Value'] = value
                    return True

I'll open a PR if needed.

trou avatar Oct 11 '18 11:10 trou

This logic only fires when a port isn't specified, and it sets the port option after running, so it's only going to run once until the user un-sets it. After that, the host and port are completely independent. In addition, the options are left as the last listener of that type to be loaded on startup, so this is also only called after Empire starts with no http listeners.

I find it nice to be able to set up a new listener with just one option, and being that it's only "clever" the first time you set an option, I don't think it's a huge deal, but I'm welcome to other people weighing in here.

mr64bit avatar Dec 17 '18 04:12 mr64bit

Well, I found it definitely very confusing and never realized that it behave the way you describe. I usually hate "magic" but I understand it is a kind of corner case, so it could stay that way but at least it should be documented somewhere.

trou avatar Dec 17 '18 07:12 trou