diff --git a/burpui/__init__.py b/burpui/__init__.py index b1e01792..c8e780e4 100644 --- a/burpui/__init__.py +++ b/burpui/__init__.py @@ -111,7 +111,7 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu """ from flask_login import LoginManager from flask_bower import Bower - from .utils import basic_login_from_request + from .utils import basic_login_from_request, ReverseProxied from .server import BUIServer as BurpUI from .routes import view from .api import api, apibp @@ -197,12 +197,6 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu # app.config['BUNDLE_ERRORS'] = True app.config['REMEMBER_COOKIE_HTTPONLY'] = True - # manage application secret key - if not app.secret_key or app.secret_key == 'random': - from base64 import b64encode - app.secret_key = b64encode(os.urandom(256)) - elif app.secret_key == 'none': - app.secret_key = None app.jinja_env.globals.update( isinstance=isinstance, @@ -222,6 +216,15 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu app.setup(app.config['CFG']) + # manage application secret key + if not app.secret_key or app.secret_key == 'random': + from base64 import b64encode + app.secret_key = b64encode(os.urandom(256)) + elif app.secret_key == 'none': + app.secret_key = None + + app.wsgi_app = ReverseProxied(app.wsgi_app, app) + # Manage gunicorn special tricks & improvements if gunicorn: # pragma: no cover logger.info('Using gunicorn') @@ -297,6 +300,17 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu bower = Bower() bower.init_app(app) + @app.before_request + def setup_request(): + if app.scookie: + from flask import request + criteria = [ + request.is_secure, + request.headers.get('X-Forwarded-Proto', 'http') == 'https' + ] + app.config['SESSION_COOKIE_SECURE'] = \ + app.config['REMEMBER_COOKIE_SECURE'] = any(criteria) + @app.login_manager.user_loader def load_user(userid): """User loader callback""" diff --git a/burpui/server.py b/burpui/server.py index ef0f5e76..9138e7ce 100644 --- a/burpui/server.py +++ b/burpui/server.py @@ -37,6 +37,7 @@ G_SCOOKIE = 'False' G_APPSECRET = 'random' G_COOKIETIME = '14' G_INCLUDES = '/etc/burp' +G_PREFIX = '' class BUIServer(Flask): @@ -99,6 +100,7 @@ class BUIServer(Flask): 'appsecret': G_APPSECRET, 'cookietime': G_COOKIETIME, 'includes': G_INCLUDES, + 'prefix': G_PREFIX, } config = ConfigParser.ConfigParser(self.defaults) with open(conf) as fp: @@ -127,6 +129,11 @@ class BUIServer(Flask): ) self.sslcert = self._safe_config_get(config.get, 'sslcert') self.sslkey = self._safe_config_get(config.get, 'sslkey') + self.prefix = self._safe_config_get(config.get, 'prefix') + if self.prefix and not self.prefix.startswith('/'): + if self.prefix.lower != 'none': + self.logger.warning("'prefix' must start with a '/'!") + self.prefix = '' self.auth = self._safe_config_get(config.get, 'auth') if self.auth and self.auth.lower() != 'none': try: @@ -205,14 +212,12 @@ class BUIServer(Flask): ) # Security options - self.config['SESSION_COOKIE_SECURE'] = \ - self.config['REMEMBER_COOKIE_SECURE'] = \ - self._safe_config_get( - config.getboolean, - 'scookie', - 'Security', - cast=bool - ) or self.ssl + self.scookie = self._safe_config_get( + config.getboolean, + 'scookie', + 'Security', + cast=bool + ) self.config['SECRET_KEY'] = self._safe_config_get( config.get, 'appsecret', @@ -254,6 +259,12 @@ class BUIServer(Flask): self.logger.info('standalone: {}'.format(self.standalone)) self.logger.info('sslcert: {}'.format(self.sslcert)) self.logger.info('sslkey: {}'.format(self.sslkey)) + self.logger.info('prefix: {}'.format(self.prefix)) + self.logger.info('secure cookie: {}'.format(self.scookie)) + self.logger.info( + 'cookietime: {}'.format(self.config['REMEMBER_COOKIE_DURATION']) + ) + self.logger.info('includes: {}'.format(self.includes)) self.logger.info('refresh: {}'.format(self.config['REFRESH'])) self.logger.info('liverefresh: {}'.format(self.config['LIVEREFRESH'])) self.logger.info('auth: {}'.format(self.auth)) diff --git a/burpui/utils.py b/burpui/utils.py index 45028919..c4f6c937 100644 --- a/burpui/utils.py +++ b/burpui/utils.py @@ -221,3 +221,37 @@ def basic_login_from_request(request, app): return user app.logger.warning('Failed to log-in') return None + + +class ReverseProxied(object): + '''Wrap the application in this middleware and configure the + front-end server to add these headers, to let you quietly bind + this to a URL other than / and to an HTTP scheme that is + different than what is used locally. + + In nginx: + location /myprefix { + proxy_pass http://192.168.0.1:5001; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + proxy_set_header X-Script-Name /myprefix; + } + + :param app: the WSGI application + + Inspired by: http://flask.pocoo.org/snippets/35/ + ''' + def __init__(self, wsgi_app, app): + self.wsgi_app = wsgi_app + self.app = app + + def __call__(self, environ, start_response): + script_name = environ.get('HTTP_X_SCRIPT_NAME', self.app.prefix) + if script_name: + environ['SCRIPT_NAME'] = script_name + path_info = environ['PATH_INFO'] + if path_info.startswith(script_name): + environ['PATH_INFO'] = path_info[len(script_name):] + + return self.wsgi_app(environ, start_response) diff --git a/docs/gunicorn.rst b/docs/gunicorn.rst index ce54b484..79499dcb 100644 --- a/docs/gunicorn.rst +++ b/docs/gunicorn.rst @@ -110,10 +110,10 @@ restart `Gunicorn`_: service gunicorn restart -Reverse Proxy +Reverse-Proxy ------------- -You may want to add a reverse proxy so `Burp-UI`_ can be accessed on port 80 (or +You may want to add a reverse-proxy so `Burp-UI`_ can be accessed on port 80 (or 443) along with other applications. Here is a sample configuration for Nginx: @@ -142,6 +142,47 @@ Here is a sample configuration for Nginx: } +Sub-root path +^^^^^^^^^^^^^ + +You can host `Burp-UI`_ behind a sub-root path. For instance ``/burpui``. +To accomplish this, you can either setup your reverse-proxy to announce the +desired *prefix*, or you can use the ``prefix`` option in your `Burp-UI`_ +configuration file (see `usage `_ for details). + +If you want to configure this reverse-proxy side, you need to announce the HTTP +Header ``X-Script-Name``. + +Here is a sample configuration for Nginx: + +:: + + server { + listen 80; + server_name example.com; + + access_log /var/log/nginx/burpui.access.log; + error_log /var/log/nginx/burpui.error.log; + + location /burpui { + + # you need to change this to "https", if you set "ssl" directive to "on" + proxy_set_header X-FORWARDED_PROTO http; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $remote_addr; + # Our service is hosted behind the "/burpui" prefix + proxy_set_header X-Script-Name /burpui; + + proxy_read_timeout 300; + proxy_connect_timeout 300; + + proxy_pass http://localhost:5000; + } + } + + +.. warning:: If your *prefix* does not start with a '/', it will be ignored. + Production ---------- diff --git a/docs/introduction.rst b/docs/introduction.rst index 531222a3..707515e1 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -35,9 +35,9 @@ Compatibility | 2.0.18 => 2.0.X protocol 2 | | X* | +----------------------------+-------+-------+ -* The protocol 2 is in heavy development Burp side so the support in `Burp-UI`_ -is best effort and all features (such as server-initiated restoration) are not -available. +\* The protocol 2 is in heavy development Burp side so the support in +`Burp-UI`_ is best effort and all features (such as server-initiated +restoration) are not available. Known Issues diff --git a/docs/usage.rst b/docs/usage.rst index 97f9b9b9..714e5ca9 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -63,6 +63,12 @@ The `burpui.cfg`_ configuration file contains a ``[Global]`` section as follow: # list misc/acl directory to see the available backends # default is no ACL acl: basic + # You can change the prefix if you are behind a reverse-proxy under a custom + # root path. For example: /burpui + # You can also configure your reverse-proxy to announce the prefix through the + # 'X-Script-Name' header. In this case, the bellow prefix will be ignored in + # favour of the one announced by your reverse-proxy + prefix: none Each option is commented, but here is a more detailed documentation: @@ -93,6 +99,8 @@ Each option is commented, but here is a more detailed documentation: (see `Modes`_ for more details) - *auth*: What `Authentication`_ backend to use. - *acl*: What `ACL`_ module to use. +- *prefix*: You can host `Burp-UI`_ behind a sub-root path. See the `gunicorn + `__ page for details. There is also a ``[UI]`` section in which you can configure some *UI* diff --git a/share/burpui/etc/burpui.sample.cfg b/share/burpui/etc/burpui.sample.cfg index 6304f86e..4d156f03 100644 --- a/share/burpui/etc/burpui.sample.cfg +++ b/share/burpui/etc/burpui.sample.cfg @@ -27,6 +27,12 @@ auth: basic # list misc/acl directory to see the available backends # default is no ACL acl: basic +# You can change the prefix if you are behind a reverse-proxy under a custom +# root path. For example: /burpui +# You can also configure your reverse-proxy to announce the prefix through the +# 'X-Script-Name' header. In this case, the bellow prefix will be ignored in +# favour of the one announced by your reverse-proxy +prefix: none [UI] # refresh interval of the pages in seconds @@ -52,7 +58,9 @@ redis: localhost:6379 includes: /etc/burp # remember_cookie duration in days cookietime: 14 -# whether to use a secure cookie (for https) or not +# whether to use a secure cookie for https or not. If set to false, cookies +# won't have the 'secure' flag. +# This setting is only useful when HTTPS is detected scookie: false # application secret to secure cookies. If you don't set anything, the default # value is 'random' which will generate a new secret after every restart of your