Empire
Empire copied to clipboard
HTTP listeners can have different ports on Host and Port
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.
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.
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.