mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-21 06:45:24 -06:00
add the ability to write our own config for #132
This commit is contained in:
parent
b38514d559
commit
25bb371032
10 changed files with 660 additions and 482 deletions
|
|
@ -12,16 +12,16 @@ from gevent.server import StreamServer
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from .exceptions import BUIserverException
|
from .exceptions import BUIserverException
|
||||||
from .misc.backend.interface import BUIbackend
|
from .misc.backend.interface import BUIbackend
|
||||||
from ._compat import ConfigParser, pickle
|
from ._compat import pickle
|
||||||
from .utils import BUIlogging
|
from .utils import BUIlogging, BUIConfig
|
||||||
|
|
||||||
g_port = u'10000'
|
G_PORT = 10000
|
||||||
g_bind = u'::'
|
G_BIND = u'::'
|
||||||
g_ssl = u''
|
G_SSL = False
|
||||||
g_version = u'1'
|
G_VERSION = 1
|
||||||
g_sslcert = u''
|
G_SSLCERT = u''
|
||||||
g_sslkey = u''
|
G_SSLKEY = u''
|
||||||
g_password = u'password'
|
G_PASSWORD = u'password'
|
||||||
|
|
||||||
DISCLOSURE = 5
|
DISCLOSURE = 5
|
||||||
|
|
||||||
|
|
@ -63,13 +63,18 @@ class BurpHandler(BUIbackend):
|
||||||
class BUIAgent(BUIbackend, BUIlogging):
|
class BUIAgent(BUIbackend, BUIlogging):
|
||||||
BUIbackend.__abstractmethods__ = frozenset()
|
BUIbackend.__abstractmethods__ = frozenset()
|
||||||
defaults = {
|
defaults = {
|
||||||
'port': g_port, 'bind': g_bind,
|
'Global': {
|
||||||
'ssl': g_ssl, 'sslcert': g_sslcert, 'sslkey': g_sslkey,
|
'port': G_PORT,
|
||||||
'version': g_version, 'password': g_password
|
'bind': G_BIND,
|
||||||
|
'ssl': G_SSL,
|
||||||
|
'sslcert': G_SSLCERT,
|
||||||
|
'sslkey': G_SSLKEY,
|
||||||
|
'version': G_VERSION,
|
||||||
|
'password': G_PASSWORD,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, conf=None, level=0, logfile=None, debug=False):
|
def __init__(self, conf=None, level=0, logfile=None, debug=False):
|
||||||
self.conf = conf
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.padding = 1
|
self.padding = 1
|
||||||
if level > logging.NOTSET:
|
if level > logging.NOTSET:
|
||||||
|
|
@ -102,28 +107,21 @@ class BUIAgent(BUIbackend, BUIlogging):
|
||||||
handler.setLevel(lvl)
|
handler.setLevel(lvl)
|
||||||
handler.setFormatter(logging.Formatter(LOG_FORMAT))
|
handler.setFormatter(logging.Formatter(LOG_FORMAT))
|
||||||
self.logger.addHandler(handler)
|
self.logger.addHandler(handler)
|
||||||
self._logger('info', 'conf: ' + self.conf)
|
self._logger('info', 'conf: ' + conf)
|
||||||
self._logger('info', 'level: ' + logging.getLevelName(lvl))
|
self._logger('info', 'level: ' + logging.getLevelName(lvl))
|
||||||
if not self.conf:
|
if not conf:
|
||||||
raise IOError('No configuration file found')
|
raise IOError('No configuration file found')
|
||||||
|
|
||||||
config = ConfigParser.ConfigParser(self.defaults)
|
# Raise exception if errors are encountered during parsing
|
||||||
with open(self.conf) as fp:
|
self.conf = BUIConfig(conf, True, self.defaults)
|
||||||
config.readfp(fp)
|
self.conf.default_section('Global')
|
||||||
try:
|
self.port = self.conf.safe_get('port', 'integer')
|
||||||
self.port = self._safe_config_get(config.getint, 'port', 'Global', cast=int)
|
self.bind = self.conf.safe_get('bind')
|
||||||
self.bind = self._safe_config_get(config.get, 'bind', 'Global')
|
self.vers = self.conf.safe_get('version', 'integer')
|
||||||
self.vers = self._safe_config_get(config.getint, 'version', 'Global', cast=int)
|
self.ssl = self.conf.safe_get('ssl', 'boolean')
|
||||||
try:
|
self.sslcert = self.conf.safe_get('sslcert')
|
||||||
self.ssl = config.getboolean('Global', 'ssl')
|
self.sslkey = self.conf.safe_get('sslkey')
|
||||||
except ValueError:
|
self.password = self.conf.safe_get('password')
|
||||||
self._logger('warning', "Wrong value for 'ssl' key! Assuming 'false'")
|
|
||||||
self.ssl = False
|
|
||||||
self.sslcert = self._safe_config_get(config.get, 'sslcert', 'Global')
|
|
||||||
self.sslkey = self._safe_config_get(config.get, 'sslkey', 'Global')
|
|
||||||
self.password = self._safe_config_get(config.get, 'password', 'Global')
|
|
||||||
except ConfigParser.NoOptionError as e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
self.cli = BurpHandler(self.vers, self.logger, self.conf)
|
self.cli = BurpHandler(self.vers, self.logger, self.conf)
|
||||||
if not self.ssl:
|
if not self.ssl:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf8 -*-
|
# -*- coding: utf8 -*-
|
||||||
from .interface import BUIacl, BUIaclLoader
|
from .interface import BUIacl, BUIaclLoader
|
||||||
from ..._compat import ConfigParser
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
@ -8,7 +7,11 @@ import json
|
||||||
class ACLloader(BUIaclLoader):
|
class ACLloader(BUIaclLoader):
|
||||||
"""See :class:`burpui.misc.acl.interface.BUIaclLoader`"""
|
"""See :class:`burpui.misc.acl.interface.BUIaclLoader`"""
|
||||||
def __init__(self, app=None):
|
def __init__(self, app=None):
|
||||||
"""See :func:`burpui.misc.acl.interface.BUIaclLoader.__init__`"""
|
"""See :func:`burpui.misc.acl.interface.BUIaclLoader.__init__`
|
||||||
|
|
||||||
|
:param app: Application context
|
||||||
|
:type app: :class:`burpui.server.BUIServer`
|
||||||
|
"""
|
||||||
self.app = app
|
self.app = app
|
||||||
self.admins = [
|
self.admins = [
|
||||||
'admin'
|
'admin'
|
||||||
|
|
@ -16,34 +19,23 @@ class ACLloader(BUIaclLoader):
|
||||||
self.clients = {}
|
self.clients = {}
|
||||||
self.servers = {}
|
self.servers = {}
|
||||||
self.standalone = self.app.standalone
|
self.standalone = self.app.standalone
|
||||||
conf = self.app.config['CFG']
|
conf = self.app.conf
|
||||||
c = ConfigParser.ConfigParser()
|
|
||||||
adms = []
|
adms = []
|
||||||
with open(conf) as fp:
|
if 'BASIC:ACL' in conf.options:
|
||||||
c.readfp(fp)
|
adms = conf.safe_get('admin', 'force_list', section='BASIC:ACL')
|
||||||
if c.has_section('BASIC:ACL'):
|
for opt in conf.options.get('BASIC:ACL').keys():
|
||||||
|
if opt == 'admin':
|
||||||
|
continue
|
||||||
|
lit = conf.safe_get(opt, section='BASIC:ACL')
|
||||||
|
rec = []
|
||||||
try:
|
try:
|
||||||
temp = c.get('BASIC:ACL', 'admin')
|
rec = json.loads(lit)
|
||||||
try:
|
if isinstance(rec, dict):
|
||||||
adms = json.loads(temp)
|
self.servers[opt] = rec.keys()
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(str(e))
|
|
||||||
adms = [temp]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(str(e))
|
self.logger.error(str(e))
|
||||||
for opt in c.options('BASIC:ACL'):
|
rec = [lit]
|
||||||
if opt == 'admin':
|
self.clients[opt] = rec
|
||||||
continue
|
|
||||||
lit = c.get('BASIC:ACL', opt)
|
|
||||||
rec = []
|
|
||||||
try:
|
|
||||||
rec = json.loads(lit)
|
|
||||||
if isinstance(rec, dict):
|
|
||||||
self.servers[opt] = rec.keys()
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(str(e))
|
|
||||||
rec = [lit]
|
|
||||||
self.clients[opt] = rec
|
|
||||||
|
|
||||||
if adms:
|
if adms:
|
||||||
self.admins = adms
|
self.admins = adms
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
# -*- coding: utf8 -*-
|
# -*- coding: utf8 -*-
|
||||||
|
import os
|
||||||
|
|
||||||
from .interface import BUIhandler, BUIuser
|
from .interface import BUIhandler, BUIuser
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from flask import session
|
from flask import session
|
||||||
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class UserAuthHandler(BUIhandler):
|
class UserAuthHandler(BUIhandler):
|
||||||
"""See :class:`burpui.misc.auth.interface.BUIhandler`"""
|
"""See :class:`burpui.misc.auth.interface.BUIhandler`"""
|
||||||
|
|
@ -17,10 +15,7 @@ class UserAuthHandler(BUIhandler):
|
||||||
self.backends = []
|
self.backends = []
|
||||||
if self.app.auth:
|
if self.app.auth:
|
||||||
me, _ = os.path.splitext(os.path.basename(__file__))
|
me, _ = os.path.splitext(os.path.basename(__file__))
|
||||||
try:
|
back = self.app.auth
|
||||||
back = json.loads(self.app.auth)
|
|
||||||
except:
|
|
||||||
back = re.split(' *,+ *', self.app.auth)
|
|
||||||
for au in back:
|
for au in back:
|
||||||
if au == me:
|
if au == me:
|
||||||
self.app.logger.error('Recursive import not permited!')
|
self.app.logger.error('Recursive import not permited!')
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ import json
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import codecs
|
|
||||||
|
|
||||||
from ast import literal_eval
|
|
||||||
from pipes import quote
|
from pipes import quote
|
||||||
from six import iteritems, viewkeys
|
from six import iteritems, viewkeys
|
||||||
|
|
||||||
|
|
@ -26,19 +24,19 @@ from .interface import BUIbackend
|
||||||
from ..parser.burp1 import Parser
|
from ..parser.burp1 import Parser
|
||||||
from ...utils import human_readable as _hr, BUIcompress
|
from ...utils import human_readable as _hr, BUIcompress
|
||||||
from ...exceptions import BUIserverException
|
from ...exceptions import BUIserverException
|
||||||
from ..._compat import ConfigParser, unquote, PY3
|
from ..._compat import unquote, PY3
|
||||||
|
|
||||||
G_BURPPORT = u'4972'
|
G_BURPPORT = 4972
|
||||||
G_BURPHOST = u'::1'
|
G_BURPHOST = u'::1'
|
||||||
G_BURPBIN = u'/usr/sbin/burp'
|
G_BURPBIN = u'/usr/sbin/burp'
|
||||||
G_STRIPBIN = u'/usr/sbin/vss_strip'
|
G_STRIPBIN = u'/usr/sbin/vss_strip'
|
||||||
G_BURPCONFCLI = u''
|
G_BURPCONFCLI = u''
|
||||||
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
||||||
G_TMPDIR = u'/tmp/bui'
|
G_TMPDIR = u'/tmp/bui'
|
||||||
G_ZIP64 = u'False'
|
G_ZIP64 = False
|
||||||
G_INCLUDES = u'/etc/burp'
|
G_INCLUDES = [u'/etc/burp']
|
||||||
G_ENFORCE = u'False'
|
G_ENFORCE = False
|
||||||
G_REVOKE = u'False'
|
G_REVOKE = False
|
||||||
|
|
||||||
|
|
||||||
class Burp(BUIbackend):
|
class Burp(BUIbackend):
|
||||||
|
|
@ -110,8 +108,8 @@ class Burp(BUIbackend):
|
||||||
and/or some global settings
|
and/or some global settings
|
||||||
:type server: :class:`burpui.server.BUIServer`
|
:type server: :class:`burpui.server.BUIServer`
|
||||||
|
|
||||||
:param conf: Configuration file to use
|
:param conf: Configuration to use
|
||||||
:type conf: str
|
:type conf: :class:`burpui.utils.BUIConfig`
|
||||||
|
|
||||||
:param dummy: Does not instanciate the object (used for development
|
:param dummy: Does not instanciate the object (used for development
|
||||||
purpose)
|
purpose)
|
||||||
|
|
@ -122,101 +120,105 @@ class Burp(BUIbackend):
|
||||||
self.client_version = None
|
self.client_version = None
|
||||||
self.server_version = None
|
self.server_version = None
|
||||||
self.app = None
|
self.app = None
|
||||||
self.zip64 = literal_eval(G_ZIP64)
|
self.zip64 = G_ZIP64
|
||||||
self.host = G_BURPHOST
|
self.host = G_BURPHOST
|
||||||
self.port = int(G_BURPPORT)
|
self.port = G_BURPPORT
|
||||||
self.burpbin = G_BURPBIN
|
self.burpbin = G_BURPBIN
|
||||||
self.stripbin = G_STRIPBIN
|
self.stripbin = G_STRIPBIN
|
||||||
self.burpconfcli = G_BURPCONFCLI
|
self.burpconfcli = G_BURPCONFCLI
|
||||||
self.burpconfsrv = G_BURPCONFSRV
|
self.burpconfsrv = G_BURPCONFSRV
|
||||||
self.tmpdir = G_TMPDIR
|
self.tmpdir = G_TMPDIR
|
||||||
self.includes = G_INCLUDES
|
self.includes = G_INCLUDES
|
||||||
self.revoke = literal_eval(G_REVOKE)
|
self.revoke = G_REVOKE
|
||||||
self.enforce = literal_eval(G_ENFORCE)
|
self.enforce = G_ENFORCE
|
||||||
self.running = []
|
self.running = []
|
||||||
self.defaults = {
|
self.defaults = {
|
||||||
'bport': G_BURPPORT,
|
'Burp1': {
|
||||||
'bhost': G_BURPHOST,
|
'bport': G_BURPPORT,
|
||||||
'burpbin': G_BURPBIN,
|
'bhost': G_BURPHOST,
|
||||||
'stripbin': G_STRIPBIN,
|
'burpbin': G_BURPBIN,
|
||||||
'bconfcli': G_BURPCONFCLI,
|
'stripbin': G_STRIPBIN,
|
||||||
'bconfsrv': G_BURPCONFSRV,
|
'bconfcli': G_BURPCONFCLI,
|
||||||
'tmpdir': G_TMPDIR,
|
'bconfsrv': G_BURPCONFSRV,
|
||||||
'zip64': G_ZIP64,
|
'tmpdir': G_TMPDIR,
|
||||||
'includes': G_INCLUDES,
|
},
|
||||||
'revoke': G_REVOKE,
|
'Experimental': {
|
||||||
'enforce': G_ENFORCE,
|
'zip64': G_ZIP64,
|
||||||
|
},
|
||||||
|
'Security': {
|
||||||
|
'includes': G_INCLUDES,
|
||||||
|
'revoke': G_REVOKE,
|
||||||
|
'enforce': G_ENFORCE,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if conf:
|
if conf:
|
||||||
config = ConfigParser.ConfigParser(self.defaults)
|
conf.update_defaults(self.defaults)
|
||||||
with codecs.open(conf, 'r', 'utf-8') as fileobj:
|
conf.default_section('Burp1')
|
||||||
config.readfp(fileobj)
|
self.port = conf.safe_get('bport', 'integer')
|
||||||
|
self.host = conf.safe_get('bhost')
|
||||||
|
self.burpbin = self._get_binary_path(
|
||||||
|
conf,
|
||||||
|
'burpbin',
|
||||||
|
G_BURPBIN
|
||||||
|
)
|
||||||
|
self.stripbin = self._get_binary_path(
|
||||||
|
conf,
|
||||||
|
'stripbin',
|
||||||
|
G_STRIPBIN
|
||||||
|
)
|
||||||
|
confcli = conf.safe_get('bconfcli')
|
||||||
|
confsrv = conf.safe_get('bconfsrv')
|
||||||
|
tmpdir = conf.safe_get('tmpdir')
|
||||||
|
|
||||||
self.port = self._safe_config_get(config.getint, 'bport', cast=int)
|
# Experimental options
|
||||||
self.host = self._safe_config_get(config.get, 'bhost')
|
self.zip64 = conf.safe_get(
|
||||||
self.burpbin = self._get_binary_path(
|
'zip64',
|
||||||
config,
|
'boolean',
|
||||||
'burpbin',
|
section='Experimental'
|
||||||
G_BURPBIN
|
)
|
||||||
)
|
|
||||||
self.stripbin = self._get_binary_path(
|
|
||||||
config,
|
|
||||||
'stripbin',
|
|
||||||
G_STRIPBIN
|
|
||||||
)
|
|
||||||
confcli = self._safe_config_get(config.get, 'bconfcli')
|
|
||||||
confsrv = self._safe_config_get(config.get, 'bconfsrv')
|
|
||||||
tmpdir = self._safe_config_get(config.get, 'tmpdir')
|
|
||||||
|
|
||||||
# Experimental options
|
# Security options
|
||||||
self.zip64 = self._safe_config_get(
|
self.includes = conf.safe_get(
|
||||||
config.getboolean,
|
'includes',
|
||||||
'zip64',
|
'force_list',
|
||||||
sect='Experimental'
|
section='Security'
|
||||||
)
|
)
|
||||||
|
self.enforce = conf.safe_get(
|
||||||
|
'enforce',
|
||||||
|
'boolean',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
self.revoke = conf.safe_get(
|
||||||
|
'revoke',
|
||||||
|
'boolean',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
|
||||||
# Security options
|
if tmpdir and os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
||||||
self.includes = self._safe_config_get(
|
self.logger.warning("'%s' is not a directory", tmpdir)
|
||||||
config.get,
|
if tmpdir == G_TMPDIR:
|
||||||
'includes',
|
raise IOError("Cannot use '{}' as tmpdir".format(tmpdir))
|
||||||
sect='Security'
|
tmpdir = G_TMPDIR
|
||||||
)
|
if os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
||||||
self.enforce = self._safe_config_get(
|
raise IOError("Cannot use '{}' as tmpdir".format(tmpdir))
|
||||||
config.getboolean,
|
if tmpdir and not os.path.exists(tmpdir):
|
||||||
'enforce',
|
os.makedirs(tmpdir)
|
||||||
sect='Security'
|
|
||||||
)
|
|
||||||
self.revoke = self._safe_config_get(
|
|
||||||
config.getboolean,
|
|
||||||
'revoke',
|
|
||||||
sect='Security'
|
|
||||||
)
|
|
||||||
|
|
||||||
if tmpdir and os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
if confcli and not os.path.isfile(confcli):
|
||||||
self.logger.warning("'%s' is not a directory", tmpdir)
|
self.logger.warning("The file '%s' does not exist", confcli)
|
||||||
if tmpdir == G_TMPDIR:
|
confcli = None
|
||||||
raise IOError("Cannot use '{}' as tmpdir".format(tmpdir))
|
|
||||||
tmpdir = G_TMPDIR
|
|
||||||
if os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
|
||||||
raise IOError("Cannot use '{}' as tmpdir".format(tmpdir))
|
|
||||||
if tmpdir and not os.path.exists(tmpdir):
|
|
||||||
os.makedirs(tmpdir)
|
|
||||||
|
|
||||||
if confcli and not os.path.isfile(confcli):
|
if confsrv and not os.path.isfile(confsrv):
|
||||||
self.logger.warning("The file '%s' does not exist", confcli)
|
self.logger.warning("The file '%s' does not exist", confsrv)
|
||||||
confcli = None
|
confsrv = None
|
||||||
|
|
||||||
if confsrv and not os.path.isfile(confsrv):
|
if self.host not in ['127.0.0.1', '::1']:
|
||||||
self.logger.warning("The file '%s' does not exist", confsrv)
|
self.logger.warning("Invalid value for 'bhost'. Must be '127.0.0.1' or '::1'. Falling back to '%s'", G_BURPHOST)
|
||||||
confsrv = None
|
self.host = G_BURPHOST
|
||||||
|
|
||||||
if self.host not in ['127.0.0.1', '::1']:
|
self.burpconfcli = confcli
|
||||||
self.logger.warning("Invalid value for 'bhost'. Must be '127.0.0.1' or '::1'. Falling back to '%s'", G_BURPHOST)
|
self.burpconfsrv = confsrv
|
||||||
self.host = G_BURPHOST
|
self.tmpdir = tmpdir
|
||||||
|
|
||||||
self.burpconfcli = confcli
|
|
||||||
self.burpconfsrv = confsrv
|
|
||||||
self.tmpdir = tmpdir
|
|
||||||
|
|
||||||
self.parser = Parser(self)
|
self.parser = Parser(self)
|
||||||
|
|
||||||
|
|
@ -268,7 +270,7 @@ class Burp(BUIbackend):
|
||||||
:param default: Default value in case the retrieved value is not correct
|
:param default: Default value in case the retrieved value is not correct
|
||||||
:type default: str
|
:type default: str
|
||||||
"""
|
"""
|
||||||
temp = self._safe_config_get(config.get, field, sect)
|
temp = config.safe_get(field, section=sect)
|
||||||
|
|
||||||
if temp and not temp.startswith('/'):
|
if temp and not temp.startswith('/'):
|
||||||
self.logger.warning("Please provide an absolute path for the '{}' option. Fallback to '{}'".format(field, default))
|
self.logger.warning("Please provide an absolute path for the '{}' option. Fallback to '{}'".format(field, default))
|
||||||
|
|
@ -533,6 +535,11 @@ class Burp(BUIbackend):
|
||||||
backup[ckey[0]][ckey[1]] = int(val)
|
backup[ckey[0]][ckey[1]] = int(val)
|
||||||
else:
|
else:
|
||||||
backup[ckey] = int(val)
|
backup[ckey] = int(val)
|
||||||
|
|
||||||
|
# Needed for graphs
|
||||||
|
if 'received' not in backup:
|
||||||
|
backup['received'] = 1
|
||||||
|
|
||||||
return backup
|
return backup
|
||||||
|
|
||||||
def _parse_backup_log(self, filemap, number, client=None, agent=None):
|
def _parse_backup_log(self, filemap, number, client=None, agent=None):
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,9 @@ import re
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
import codecs
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ast import literal_eval
|
|
||||||
from select import select
|
from select import select
|
||||||
from six import iteritems, viewkeys
|
from six import iteritems, viewkeys
|
||||||
|
|
||||||
|
|
@ -23,7 +21,6 @@ from .burp1 import Burp as Burp1
|
||||||
from ..parser.burp2 import Parser
|
from ..parser.burp2 import Parser
|
||||||
from ...utils import human_readable as _hr
|
from ...utils import human_readable as _hr
|
||||||
from ...exceptions import BUIserverException
|
from ...exceptions import BUIserverException
|
||||||
from ..._compat import ConfigParser
|
|
||||||
|
|
||||||
if sys.version_info < (3, 3):
|
if sys.version_info < (3, 3):
|
||||||
TimeoutError = OSError
|
TimeoutError = OSError
|
||||||
|
|
@ -35,11 +32,11 @@ G_STRIPBIN = u'/usr/sbin/vss_strip'
|
||||||
G_BURPCONFCLI = u'/etc/burp/burp.conf'
|
G_BURPCONFCLI = u'/etc/burp/burp.conf'
|
||||||
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
||||||
G_TMPDIR = u'/tmp/bui'
|
G_TMPDIR = u'/tmp/bui'
|
||||||
G_TIMEOUT = u'5'
|
G_TIMEOUT = 5
|
||||||
G_ZIP64 = u'False'
|
G_ZIP64 = False
|
||||||
G_INCLUDES = u'/etc/burp'
|
G_INCLUDES = [u'/etc/burp']
|
||||||
G_ENFORCE = u'False'
|
G_ENFORCE = False
|
||||||
G_REVOKE = u'False'
|
G_REVOKE = False
|
||||||
|
|
||||||
|
|
||||||
# Some functions are the same as in Burp1 backend
|
# Some functions are the same as in Burp1 backend
|
||||||
|
|
@ -54,149 +51,146 @@ class Burp(Burp1):
|
||||||
and/or some global settings
|
and/or some global settings
|
||||||
:type server: :class:`burpui.server.BUIServer`
|
:type server: :class:`burpui.server.BUIServer`
|
||||||
|
|
||||||
:param conf: Configuration file to use
|
:param conf: Configuration to use
|
||||||
:type conf: str
|
:type conf: :class:`burpui.utils.BUIConfig`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server=None, conf=None):
|
def __init__(self, server=None, conf=None):
|
||||||
global G_BURPBIN, G_STRIPBIN, G_BURPCONFCLI, G_BURPCONFSRV, G_TMPDIR, \
|
"""
|
||||||
G_TIMEOUT, BURP_MINIMAL_VERSION
|
:param server: ``Burp-UI`` server instance in order to access logger
|
||||||
|
and/or some global settings
|
||||||
|
:type server: :class:`burpui.server.BUIServer`
|
||||||
|
|
||||||
|
:param conf: Configuration to use
|
||||||
|
:type conf: :class:`burpui.utils.BUIConfig`
|
||||||
|
"""
|
||||||
self.proc = None
|
self.proc = None
|
||||||
self.app = server
|
self.app = server
|
||||||
self.client_version = None
|
self.client_version = None
|
||||||
self.server_version = None
|
self.server_version = None
|
||||||
self.zip64 = literal_eval(G_ZIP64)
|
self.zip64 = G_ZIP64
|
||||||
self.timeout = int(G_TIMEOUT)
|
self.timeout = G_TIMEOUT
|
||||||
self.burpbin = G_BURPBIN
|
self.burpbin = G_BURPBIN
|
||||||
self.stripbin = G_STRIPBIN
|
self.stripbin = G_STRIPBIN
|
||||||
self.burpconfcli = G_BURPCONFCLI
|
self.burpconfcli = G_BURPCONFCLI
|
||||||
self.burpconfsrv = G_BURPCONFSRV
|
self.burpconfsrv = G_BURPCONFSRV
|
||||||
self.includes = G_INCLUDES
|
self.includes = G_INCLUDES
|
||||||
self.revoke = literal_eval(G_REVOKE)
|
self.revoke = G_REVOKE
|
||||||
self.enforce = literal_eval(G_ENFORCE)
|
self.enforce = G_ENFORCE
|
||||||
self.defaults = {
|
self.defaults = {
|
||||||
'burpbin': G_BURPBIN,
|
'Burp2': {
|
||||||
'stripbin': G_STRIPBIN,
|
'burpbin': G_BURPBIN,
|
||||||
'bconfcli': G_BURPCONFCLI,
|
'stripbin': G_STRIPBIN,
|
||||||
'bconfsrv': G_BURPCONFSRV,
|
'bconfcli': G_BURPCONFCLI,
|
||||||
'timeout': G_TIMEOUT,
|
'bconfsrv': G_BURPCONFSRV,
|
||||||
'tmpdir': G_TMPDIR,
|
'timeout': G_TIMEOUT,
|
||||||
'zip64': G_ZIP64,
|
'tmpdir': G_TMPDIR,
|
||||||
'includes': G_INCLUDES,
|
},
|
||||||
'revoke': G_REVOKE,
|
'Experimental': {
|
||||||
'enforce': G_ENFORCE,
|
'zip64': G_ZIP64,
|
||||||
|
},
|
||||||
|
'Security': {
|
||||||
|
'includes': G_INCLUDES,
|
||||||
|
'revoke': G_REVOKE,
|
||||||
|
'enforce': G_ENFORCE,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
self.running = []
|
self.running = []
|
||||||
version = ''
|
version = ''
|
||||||
if conf:
|
if conf:
|
||||||
config = ConfigParser.ConfigParser(self.defaults)
|
conf.update_defaults(self.defaults)
|
||||||
with codecs.open(conf, 'r', 'utf-8') as conffile:
|
conf.default_section('Burp2')
|
||||||
config.readfp(conffile)
|
self.burpbin = self._get_binary_path(
|
||||||
try:
|
conf,
|
||||||
self.burpbin = self._get_binary_path(
|
'burpbin',
|
||||||
config,
|
G_BURPBIN,
|
||||||
'burpbin',
|
sect='Burp2'
|
||||||
G_BURPBIN,
|
)
|
||||||
sect='Burp2'
|
self.stripbin = self._get_binary_path(
|
||||||
)
|
conf,
|
||||||
self.stripbin = self._get_binary_path(
|
'stripbin',
|
||||||
config,
|
G_STRIPBIN,
|
||||||
'stripbin',
|
sect='Burp2'
|
||||||
G_STRIPBIN,
|
)
|
||||||
sect='Burp2'
|
confcli = conf.safe_get(
|
||||||
)
|
'bconfcli'
|
||||||
confcli = self._safe_config_get(
|
)
|
||||||
config.get,
|
confsrv = conf.safe_get(
|
||||||
'bconfcli',
|
'bconfsrv'
|
||||||
sect='Burp2'
|
)
|
||||||
)
|
self.timeout = conf.safe_get(
|
||||||
confsrv = self._safe_config_get(
|
'timeout',
|
||||||
config.get,
|
'integer'
|
||||||
'bconfsrv',
|
)
|
||||||
sect='Burp2'
|
tmpdir = conf.safe_get(
|
||||||
)
|
'tmpdir'
|
||||||
self.timeout = self._safe_config_get(
|
)
|
||||||
config.getint,
|
|
||||||
'timeout',
|
|
||||||
sect='Burp2',
|
|
||||||
cast=int
|
|
||||||
)
|
|
||||||
tmpdir = self._safe_config_get(
|
|
||||||
config.get,
|
|
||||||
'tmpdir',
|
|
||||||
sect='Burp2'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Experimental options
|
# Experimental options
|
||||||
self.zip64 = self._safe_config_get(
|
self.zip64 = conf.safe_get(
|
||||||
config.getboolean,
|
'zip64',
|
||||||
'zip64',
|
'boolean',
|
||||||
sect='Experimental',
|
section='Experimental'
|
||||||
cast=bool
|
)
|
||||||
|
|
||||||
|
# Security options
|
||||||
|
self.includes = conf.safe_get(
|
||||||
|
'includes',
|
||||||
|
'force_list',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
self.enforce = conf.safe_get(
|
||||||
|
'enforce',
|
||||||
|
'boolean',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
self.revoke = conf.safe_get(
|
||||||
|
'revoke',
|
||||||
|
'boolean',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (tmpdir and os.path.exists(tmpdir) and
|
||||||
|
not os.path.isdir(tmpdir)):
|
||||||
|
self.logger.warning(
|
||||||
|
"'%s' is not a directory",
|
||||||
|
tmpdir
|
||||||
|
)
|
||||||
|
if tmpdir == G_TMPDIR:
|
||||||
|
raise IOError(
|
||||||
|
"Cannot use '{}' as tmpdir".format(tmpdir)
|
||||||
)
|
)
|
||||||
|
tmpdir = G_TMPDIR
|
||||||
# Security options
|
if os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
||||||
self.includes = self._safe_config_get(
|
raise IOError(
|
||||||
config.get,
|
"Cannot use '{}' as tmpdir".format(tmpdir)
|
||||||
'includes',
|
|
||||||
sect='Security'
|
|
||||||
)
|
|
||||||
self.enforce = self._safe_config_get(
|
|
||||||
config.getboolean,
|
|
||||||
'enforce',
|
|
||||||
sect='Security'
|
|
||||||
)
|
|
||||||
self.revoke = self._safe_config_get(
|
|
||||||
config.getboolean,
|
|
||||||
'revoke',
|
|
||||||
sect='Security'
|
|
||||||
)
|
)
|
||||||
|
if tmpdir and not os.path.exists(tmpdir):
|
||||||
|
os.makedirs(tmpdir)
|
||||||
|
|
||||||
if (tmpdir and os.path.exists(tmpdir) and
|
if confcli and not os.path.isfile(confcli):
|
||||||
not os.path.isdir(tmpdir)):
|
self.logger.warning(
|
||||||
self.logger.warning(
|
"The file '%s' does not exist",
|
||||||
"'%s' is not a directory",
|
confcli
|
||||||
tmpdir
|
)
|
||||||
)
|
confcli = G_BURPCONFCLI
|
||||||
if tmpdir == G_TMPDIR:
|
|
||||||
raise IOError(
|
|
||||||
"Cannot use '{}' as tmpdir".format(tmpdir)
|
|
||||||
)
|
|
||||||
tmpdir = G_TMPDIR
|
|
||||||
if os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
|
||||||
raise IOError(
|
|
||||||
"Cannot use '{}' as tmpdir".format(tmpdir)
|
|
||||||
)
|
|
||||||
if tmpdir and not os.path.exists(tmpdir):
|
|
||||||
os.makedirs(tmpdir)
|
|
||||||
|
|
||||||
if confcli and not os.path.isfile(confcli):
|
if confsrv and not os.path.isfile(confsrv):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"The file '%s' does not exist",
|
"The file '%s' does not exist",
|
||||||
confcli
|
confsrv
|
||||||
)
|
)
|
||||||
confcli = G_BURPCONFCLI
|
confsrv = G_BURPCONFSRV
|
||||||
|
|
||||||
if confsrv and not os.path.isfile(confsrv):
|
if not self.burpbin:
|
||||||
self.logger.warning(
|
# The burp binary is mandatory for this backend
|
||||||
"The file '%s' does not exist",
|
raise Exception(
|
||||||
confsrv
|
'This backend *CAN NOT* work without a burp binary'
|
||||||
)
|
)
|
||||||
confsrv = G_BURPCONFSRV
|
|
||||||
|
|
||||||
if not self.burpbin:
|
self.tmpdir = tmpdir
|
||||||
# The burp binary is mandatory for this backend
|
self.burpconfcli = confcli
|
||||||
raise Exception(
|
self.burpconfsrv = confsrv
|
||||||
'This backend *CAN NOT* work without a burp binary'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.tmpdir = tmpdir
|
|
||||||
self.burpconfcli = confcli
|
|
||||||
self.burpconfsrv = confsrv
|
|
||||||
except ConfigParser.NoOptionError as exp:
|
|
||||||
self.logger.error(str(exp))
|
|
||||||
except ConfigParser.NoSectionError as exp:
|
|
||||||
self.logger.warning(str(exp))
|
|
||||||
|
|
||||||
# check the burp version because this backend only supports clients
|
# check the burp version because this backend only supports clients
|
||||||
# newer than BURP_MINIMAL_VERSION
|
# newer than BURP_MINIMAL_VERSION
|
||||||
|
|
@ -576,6 +570,10 @@ class Burp(Burp1):
|
||||||
if 'start' in backup and 'end' in backup:
|
if 'start' in backup and 'end' in backup:
|
||||||
backup['duration'] = backup['end'] - backup['start']
|
backup['duration'] = backup['end'] - backup['start']
|
||||||
|
|
||||||
|
# Needed for graphs
|
||||||
|
if 'received' not in backup:
|
||||||
|
backup['received'] = 1
|
||||||
|
|
||||||
return backup
|
return backup
|
||||||
|
|
||||||
# TODO: support old clients
|
# TODO: support old clients
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ class Parser(Doc):
|
||||||
# don't check
|
# don't check
|
||||||
return True
|
return True
|
||||||
path = os.path.normpath(path)
|
path = os.path.normpath(path)
|
||||||
cond = [path.startswith(x) for x in self.backend.includes.split(',')]
|
cond = [path.startswith(x) for x in self.backend.includes]
|
||||||
if not any(cond):
|
if not any(cond):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Tried to access non-allowed path: {}'.format(path)
|
'Tried to access non-allowed path: {}'.format(path)
|
||||||
|
|
@ -459,7 +459,8 @@ class Parser(Doc):
|
||||||
res2[u'includes'] = parsed.flatten('include', False).keys()
|
res2[u'includes'] = parsed.flatten('include', False).keys()
|
||||||
res2[u'includes_ext'] = parsed.include
|
res2[u'includes_ext'] = parsed.include
|
||||||
|
|
||||||
self.filecache[path]['parsed'] = res2
|
if path in self.filecache:
|
||||||
|
self.filecache[path]['parsed'] = res2
|
||||||
|
|
||||||
res.update(res2)
|
res.update(res2)
|
||||||
return res
|
return res
|
||||||
|
|
@ -503,7 +504,8 @@ class Parser(Doc):
|
||||||
res2[u'includes'] = parsed.flatten('include', False).keys()
|
res2[u'includes'] = parsed.flatten('include', False).keys()
|
||||||
res2[u'includes_ext'] = parsed.include
|
res2[u'includes_ext'] = parsed.include
|
||||||
|
|
||||||
self.filecache[path]['parsed'] = res2
|
if path in self.filecache:
|
||||||
|
self.filecache[path]['parsed'] = res2
|
||||||
|
|
||||||
res.update(res2)
|
res.update(res2)
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
368
burpui/server.py
368
burpui/server.py
|
|
@ -13,29 +13,29 @@ import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from .misc.auth.handler import UserAuthHandler
|
from .misc.auth.handler import UserAuthHandler
|
||||||
from ._compat import ConfigParser
|
from .utils import BUIConfig
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
|
||||||
G_PORT = '5000'
|
G_PORT = 5000
|
||||||
G_BIND = '::'
|
G_BIND = u'::'
|
||||||
G_REFRESH = '180'
|
G_REFRESH = 180
|
||||||
G_LIVEREFRESH = '5'
|
G_LIVEREFRESH = 5
|
||||||
G_SSL = 'False'
|
G_SSL = False
|
||||||
G_STANDALONE = 'True'
|
G_STANDALONE = True
|
||||||
G_SSLCERT = ''
|
G_SSLCERT = u''
|
||||||
G_SSLKEY = ''
|
G_SSLKEY = u''
|
||||||
G_VERSION = '1'
|
G_VERSION = 1
|
||||||
G_AUTH = 'basic'
|
G_AUTH = [u'basic']
|
||||||
G_ACL = ''
|
G_ACL = u'none'
|
||||||
G_STORAGE = ''
|
G_STORAGE = u''
|
||||||
G_REDIS = ''
|
G_REDIS = u''
|
||||||
G_SCOOKIE = 'False'
|
G_SCOOKIE = False
|
||||||
G_APPSECRET = 'random'
|
G_APPSECRET = u'random'
|
||||||
G_COOKIETIME = '14'
|
G_COOKIETIME = 14
|
||||||
G_PREFIX = ''
|
G_PREFIX = u''
|
||||||
|
|
||||||
|
|
||||||
class BUIServer(Flask):
|
class BUIServer(Flask):
|
||||||
|
|
@ -44,6 +44,34 @@ class BUIServer(Flask):
|
||||||
"""
|
"""
|
||||||
gunicorn = False
|
gunicorn = False
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'Global': {
|
||||||
|
'port': G_PORT,
|
||||||
|
'bind': G_BIND,
|
||||||
|
'ssl': G_SSL,
|
||||||
|
'standalone': G_STANDALONE,
|
||||||
|
'sslcert': G_SSLCERT,
|
||||||
|
'sslkey': G_SSLKEY,
|
||||||
|
'version': G_VERSION,
|
||||||
|
'auth': G_AUTH,
|
||||||
|
'acl': G_ACL,
|
||||||
|
'prefix': G_PREFIX,
|
||||||
|
},
|
||||||
|
'UI': {
|
||||||
|
'refresh': G_REFRESH,
|
||||||
|
'liverefresh': G_LIVEREFRESH,
|
||||||
|
},
|
||||||
|
'Security': {
|
||||||
|
'scookie': G_SCOOKIE,
|
||||||
|
'appsecret': G_APPSECRET,
|
||||||
|
'cookietime': G_COOKIETIME,
|
||||||
|
},
|
||||||
|
'Production': {
|
||||||
|
'storage': G_STORAGE,
|
||||||
|
'redis': G_REDIS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""The :class:`burpui.server.BUIServer` class provides the ``Burp-UI``
|
"""The :class:`burpui.server.BUIServer` class provides the ``Burp-UI``
|
||||||
server.
|
server.
|
||||||
|
|
@ -79,158 +107,137 @@ class BUIServer(Flask):
|
||||||
if not conf:
|
if not conf:
|
||||||
raise IOError('No configuration file found')
|
raise IOError('No configuration file found')
|
||||||
|
|
||||||
self.defaults = {
|
# Raise exception if errors are encountered during parsing
|
||||||
'port': G_PORT,
|
self.conf = BUIConfig(conf, True, self.defaults)
|
||||||
'bind': G_BIND,
|
self.conf.default_section('Global')
|
||||||
'refresh': G_REFRESH,
|
|
||||||
'ssl': G_SSL,
|
self.port = self.conf.safe_get(
|
||||||
'sslcert': G_SSLCERT,
|
'port',
|
||||||
'sslkey': G_SSLKEY,
|
'integer'
|
||||||
'version': G_VERSION,
|
)
|
||||||
'auth': G_AUTH,
|
self.bind = self.conf.safe_get('bind')
|
||||||
'standalone': G_STANDALONE,
|
self.vers = self.conf.safe_get(
|
||||||
'acl': G_ACL,
|
'version',
|
||||||
'liverefresh': G_LIVEREFRESH,
|
'integer'
|
||||||
'storage': G_STORAGE,
|
)
|
||||||
'redis': G_REDIS,
|
self.ssl = self.conf.safe_get(
|
||||||
'scookie': G_SCOOKIE,
|
'ssl',
|
||||||
'appsecret': G_APPSECRET,
|
'boolean'
|
||||||
'cookietime': G_COOKIETIME,
|
)
|
||||||
'prefix': G_PREFIX,
|
self.standalone = self.conf.safe_get(
|
||||||
}
|
'standalone',
|
||||||
config = ConfigParser.ConfigParser(self.defaults)
|
'boolean'
|
||||||
with open(conf) as fp:
|
)
|
||||||
config.readfp(fp)
|
self.sslcert = self.conf.safe_get(
|
||||||
|
'sslcert'
|
||||||
|
)
|
||||||
|
self.sslkey = self.conf.safe_get(
|
||||||
|
'sslkey'
|
||||||
|
)
|
||||||
|
self.prefix = self.conf.safe_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.conf.safe_get(
|
||||||
|
'auth',
|
||||||
|
'string_lower_list'
|
||||||
|
)
|
||||||
|
if self.auth and 'none' not in self.auth:
|
||||||
try:
|
try:
|
||||||
self.port = self._safe_config_get(
|
self.uhandler = UserAuthHandler(self)
|
||||||
config.getint,
|
except Exception as e:
|
||||||
'port',
|
self.logger.critical(
|
||||||
cast=int
|
'Import Exception, module \'{0}\': {1}'.format(
|
||||||
|
self.auth,
|
||||||
|
str(e)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
self.bind = self._safe_config_get(config.get, 'bind')
|
raise e
|
||||||
self.vers = self._safe_config_get(
|
self.acl_engine = self.conf.safe_get(
|
||||||
config.getint,
|
'acl'
|
||||||
'version',
|
)
|
||||||
cast=int
|
else:
|
||||||
)
|
self.config['LOGIN_DISABLED'] = True
|
||||||
self.ssl = self._safe_config_get(
|
# No login => no ACL
|
||||||
config.getboolean,
|
self.acl_engine = 'none'
|
||||||
'ssl',
|
self.auth = 'none'
|
||||||
cast=bool
|
|
||||||
)
|
|
||||||
self.standalone = self._safe_config_get(
|
|
||||||
config.getboolean,
|
|
||||||
'standalone',
|
|
||||||
cast=bool
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
self.uhandler = UserAuthHandler(self)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.critical(
|
|
||||||
'Import Exception, module \'{0}\': {1}'.format(
|
|
||||||
self.auth,
|
|
||||||
str(e)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
self.acl_engine = self._safe_config_get(config.get, 'acl')
|
|
||||||
else:
|
|
||||||
self.config['LOGIN_DISABLED'] = True
|
|
||||||
# No login => no ACL
|
|
||||||
self.acl_engine = 'none'
|
|
||||||
self.auth = 'none'
|
|
||||||
|
|
||||||
if self.acl_engine and self.acl_engine.lower() != 'none':
|
if self.acl_engine and self.acl_engine.lower() != 'none':
|
||||||
try:
|
try:
|
||||||
# Try to load submodules from our current environment
|
# Try to load submodules from our current environment
|
||||||
# first
|
# first
|
||||||
sys.path.insert(
|
sys.path.insert(
|
||||||
0,
|
0,
|
||||||
os.path.dirname(os.path.abspath(__file__))
|
os.path.dirname(os.path.abspath(__file__))
|
||||||
)
|
)
|
||||||
mod = __import__(
|
mod = __import__(
|
||||||
'burpui.misc.acl.{0}'.format(
|
'burpui.misc.acl.{0}'.format(
|
||||||
self.acl_engine.lower()
|
self.acl_engine.lower()
|
||||||
),
|
),
|
||||||
fromlist=['ACLloader']
|
fromlist=['ACLloader']
|
||||||
)
|
)
|
||||||
ACLloader = mod.ACLloader
|
ACLloader = mod.ACLloader
|
||||||
self.acl_handler = ACLloader(self)
|
self.acl_handler = ACLloader(self)
|
||||||
# for development purpose only
|
# for development purpose only
|
||||||
from .misc.acl.interface import BUIacl
|
from .misc.acl.interface import BUIacl
|
||||||
self.acl = BUIacl
|
self.acl = BUIacl
|
||||||
self.acl = self.acl_handler.acl
|
self.acl = self.acl_handler.acl
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.critical(
|
self.logger.critical(
|
||||||
'Import Exception, module \'{0}\': {1}'.format(
|
'Import Exception, module \'{0}\': {1}'.format(
|
||||||
self.acl_engine,
|
self.acl_engine,
|
||||||
str(e)
|
str(e)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
self.acl_handler = False
|
self.acl_handler = False
|
||||||
self.acl = False
|
self.acl = False
|
||||||
|
|
||||||
# UI options
|
# UI options
|
||||||
self.config['REFRESH'] = self._safe_config_get(
|
self.config['REFRESH'] = self.conf.safe_get(
|
||||||
config.getint,
|
'refresh',
|
||||||
'refresh',
|
'integer',
|
||||||
'UI',
|
'UI'
|
||||||
cast=int
|
)
|
||||||
)
|
self.config['LIVEREFRESH'] = self.conf.safe_get(
|
||||||
self.config['LIVEREFRESH'] = self._safe_config_get(
|
'liverefresh',
|
||||||
config.getint,
|
'integer',
|
||||||
'liverefresh',
|
'UI'
|
||||||
'UI',
|
)
|
||||||
cast=int
|
|
||||||
)
|
|
||||||
|
|
||||||
# Production options
|
# Production options
|
||||||
self.storage = self._safe_config_get(
|
self.storage = self.conf.safe_get(
|
||||||
config.get,
|
'storage',
|
||||||
'storage',
|
section='Production'
|
||||||
'Production'
|
)
|
||||||
)
|
self.redis = self.conf.safe_get(
|
||||||
self.redis = self._safe_config_get(
|
'redis',
|
||||||
config.get,
|
section='Production'
|
||||||
'redis',
|
)
|
||||||
'Production'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Security options
|
# Security options
|
||||||
self.scookie = self._safe_config_get(
|
self.scookie = self.conf.safe_get(
|
||||||
config.getboolean,
|
'scookie',
|
||||||
'scookie',
|
'boolean',
|
||||||
'Security',
|
section='Security'
|
||||||
cast=bool
|
)
|
||||||
|
self.config['SECRET_KEY'] = self.conf.safe_get(
|
||||||
|
'appsecret',
|
||||||
|
section='Security'
|
||||||
|
)
|
||||||
|
self.config['REMEMBER_COOKIE_DURATION'] = \
|
||||||
|
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(
|
||||||
|
days=self.conf.safe_get(
|
||||||
|
'cookietime',
|
||||||
|
'integer',
|
||||||
|
section='Security'
|
||||||
)
|
)
|
||||||
self.config['SECRET_KEY'] = self._safe_config_get(
|
)
|
||||||
config.get,
|
|
||||||
'appsecret',
|
|
||||||
'Security'
|
|
||||||
)
|
|
||||||
self.config['REMEMBER_COOKIE_DURATION'] = \
|
|
||||||
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(
|
|
||||||
days=self._safe_config_get(
|
|
||||||
config.getint,
|
|
||||||
'cookietime',
|
|
||||||
'Security',
|
|
||||||
cast=int
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
except ConfigParser.NoOptionError as e:
|
|
||||||
self.logger.error(str(e))
|
|
||||||
|
|
||||||
self.config['STANDALONE'] = self.standalone
|
self.config['STANDALONE'] = self.standalone
|
||||||
|
|
||||||
|
|
@ -265,7 +272,7 @@ class BUIServer(Flask):
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
mod = __import__(module, fromlist=['Burp'])
|
mod = __import__(module, fromlist=['Burp'])
|
||||||
Client = mod.Burp
|
Client = mod.Burp
|
||||||
self.cli = Client(self, conf=conf)
|
self.cli = Client(self, conf=self.conf)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.logger.critical(
|
self.logger.critical(
|
||||||
|
|
@ -278,39 +285,6 @@ class BUIServer(Flask):
|
||||||
|
|
||||||
self.init = True
|
self.init = True
|
||||||
|
|
||||||
def _safe_config_get(self, callback, key, sect='Global', cast=None):
|
|
||||||
""":func:`burpui.server.BUIServer._safe_config_get` is a wrapper to
|
|
||||||
handle Exceptions throwed by :mod:`ConfigParser`.
|
|
||||||
|
|
||||||
:param callback: Function to wrap
|
|
||||||
:type callback: callable
|
|
||||||
|
|
||||||
:param key: Key to retrieve
|
|
||||||
:type key: str
|
|
||||||
|
|
||||||
:param sect: Section of the config file to read
|
|
||||||
:type sect: str
|
|
||||||
|
|
||||||
:param cast: Cast the returned value if provided
|
|
||||||
:type case: callable
|
|
||||||
|
|
||||||
:returns: The value returned by the `callback`
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return callback(sect, key)
|
|
||||||
except ConfigParser.NoOptionError as e:
|
|
||||||
self.logger.error(str(e))
|
|
||||||
except ConfigParser.NoSectionError as e:
|
|
||||||
self.logger.warning(str(e))
|
|
||||||
if key in self.defaults:
|
|
||||||
if cast:
|
|
||||||
try:
|
|
||||||
return cast(self.defaults[key])
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
return self.defaults[key]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def manual_run(self):
|
def manual_run(self):
|
||||||
"""The :func:`burpui.server.BUIServer.manual_run` functions is used to
|
"""The :func:`burpui.server.BUIServer.manual_run` functions is used to
|
||||||
actually launch the ``Burp-UI`` server.
|
actually launch the ``Burp-UI`` server.
|
||||||
|
|
|
||||||
210
burpui/utils.py
210
burpui/utils.py
|
|
@ -8,18 +8,26 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import math
|
import math
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
import codecs
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
import tarfile
|
import tarfile
|
||||||
import logging
|
import logging
|
||||||
|
import configobj
|
||||||
|
import validate
|
||||||
|
|
||||||
from inspect import currentframe, getouterframes
|
from inspect import currentframe, getouterframes
|
||||||
from ._compat import PY3
|
from ._compat import PY3
|
||||||
|
from . import __version__, __release__
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
long = int # pragma: no cover
|
long = int # pragma: no cover
|
||||||
|
basestring = str # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
class human_readable(long):
|
class human_readable(long):
|
||||||
|
|
@ -273,3 +281,205 @@ class ReverseProxied(object):
|
||||||
self.app.warning("'prefix' must start with a '/'!")
|
self.app.warning("'prefix' must start with a '/'!")
|
||||||
|
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
class BUIConfig(object):
|
||||||
|
"""Custom config parser"""
|
||||||
|
logger = logging.getLogger('burp-ui')
|
||||||
|
|
||||||
|
def __init__(self, config, explain=False, defaults=None):
|
||||||
|
"""Wrapper around the ConfigObj class
|
||||||
|
|
||||||
|
:param config: Configuration to parse
|
||||||
|
:type config: str, list or File
|
||||||
|
|
||||||
|
:param explain: Whether to explain the parsing errors or not
|
||||||
|
:type explain: bool
|
||||||
|
|
||||||
|
:param defaults: Default options
|
||||||
|
:type defaults: dict
|
||||||
|
"""
|
||||||
|
self.conf = {}
|
||||||
|
self.section = None
|
||||||
|
self.defaults = defaults
|
||||||
|
self.validator = validate.Validator()
|
||||||
|
try:
|
||||||
|
self.conf = configobj.ConfigObj(config, encoding='utf-8')
|
||||||
|
except configobj.ConfigObjError as exp:
|
||||||
|
# We were unable to parse the config, maybe we need to
|
||||||
|
# convert/update it
|
||||||
|
self.logger.warning(
|
||||||
|
'Unable to parse the configuration... Trying to convert it'
|
||||||
|
)
|
||||||
|
# if conversion is successful, give it another try
|
||||||
|
if self._convert(config):
|
||||||
|
# This time, if it fails, the exception will be forwarded
|
||||||
|
try:
|
||||||
|
self.conf = configobj.ConfigObj(config)
|
||||||
|
except configobj.ConfigObjError as exp2:
|
||||||
|
if explain:
|
||||||
|
self._explain(exp2)
|
||||||
|
else:
|
||||||
|
raise exp2
|
||||||
|
else:
|
||||||
|
if explain:
|
||||||
|
self._explain(exp)
|
||||||
|
else:
|
||||||
|
raise exp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
"""ConfigObj object"""
|
||||||
|
return self.conf
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def string_lower_list(value):
|
||||||
|
if not value:
|
||||||
|
raise validate.VdtMissingValue('Option not found')
|
||||||
|
if not isinstance(value, list):
|
||||||
|
return [str(value).lower()]
|
||||||
|
return [str(x).lower() for x in value]
|
||||||
|
|
||||||
|
def update_defaults(self, new_defaults):
|
||||||
|
"""Add new defaults"""
|
||||||
|
self.defaults.update(new_defaults)
|
||||||
|
|
||||||
|
def default_section(self, section):
|
||||||
|
"""Set the default section"""
|
||||||
|
self.section = section
|
||||||
|
|
||||||
|
def _convert(self, config):
|
||||||
|
"""Convert an old config to a new one"""
|
||||||
|
sav = '{}.back'.format(config)
|
||||||
|
current_section = None
|
||||||
|
|
||||||
|
if os.path.exists(sav):
|
||||||
|
self.logger.error(
|
||||||
|
'Looks like the configuration file has already been converted'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.copy(config, sav)
|
||||||
|
except IOError as exp:
|
||||||
|
self.logger.error(str(exp))
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with codecs.open(sav, 'r', 'utf-8') as ori:
|
||||||
|
with codecs.open(config, 'w', 'utf-8') as new:
|
||||||
|
# We add some headers
|
||||||
|
new.write('# Auto-generated file from a previous version\n')
|
||||||
|
new.write('# @version@ - {}\n'.format(__version__))
|
||||||
|
new.write('# @release@ - {}\n'.format(__release__))
|
||||||
|
for line in ori.readlines():
|
||||||
|
line = line.rstrip('\n')
|
||||||
|
search = re.search(r'^\s*\[([^\]]+)\]\s*', line)
|
||||||
|
if search:
|
||||||
|
current_section = search.group(1)
|
||||||
|
# if we find old style config lines, we convert them
|
||||||
|
elif re.match(r'^\s*\S+\s*:\s*.+$', line) and \
|
||||||
|
re.match(r'^\s*[^\[]', line):
|
||||||
|
key, val = re.split(r'\s*:\s*', line, 1)
|
||||||
|
# We support *objects* but we need to serialize them
|
||||||
|
try:
|
||||||
|
jsn = json.loads(val)
|
||||||
|
# special case, we re-format the admin value
|
||||||
|
if current_section == 'BASIC:ACL' and \
|
||||||
|
key == 'admin' and \
|
||||||
|
isinstance(jsn, list):
|
||||||
|
val = ','.join(jsn)
|
||||||
|
elif isinstance(jsn, list) or \
|
||||||
|
isinstance(jsn, dict):
|
||||||
|
val = "'{}'".format(json.dumps(jsn))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
line = '{} = {}'.format(key, val)
|
||||||
|
|
||||||
|
new.write('{}\n'.format(line))
|
||||||
|
|
||||||
|
except IOError as exp:
|
||||||
|
self.logger.error(str(exp))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _explain(exception):
|
||||||
|
"""Explain parsing errors
|
||||||
|
|
||||||
|
:param exception: Exception object
|
||||||
|
:type exception: :class:`configobj.ConfigObjError`
|
||||||
|
"""
|
||||||
|
message = u'\n'
|
||||||
|
for error in exception.errors:
|
||||||
|
message += error.message + '\n'
|
||||||
|
|
||||||
|
raise configobj.ConfigObjError(message.rstrip('\n'))
|
||||||
|
|
||||||
|
def safe_get(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
cast='pass',
|
||||||
|
section=None,
|
||||||
|
defaults=None):
|
||||||
|
"""Safely return the asked option
|
||||||
|
|
||||||
|
:param key: Key name
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:param cast: How to cast the option
|
||||||
|
:type cast: str
|
||||||
|
|
||||||
|
:param section: Section name
|
||||||
|
:type section: str
|
||||||
|
|
||||||
|
:param defaults: Default options
|
||||||
|
:type defaults: dict
|
||||||
|
|
||||||
|
:returns: The value of the asked option
|
||||||
|
"""
|
||||||
|
# The configobj validator is sooo broken. We need to workaround it...
|
||||||
|
defaults = defaults or self.defaults
|
||||||
|
section = section or self.section
|
||||||
|
if section not in self.conf:
|
||||||
|
self.logger.warning("No '{}' section found".format(section))
|
||||||
|
if defaults:
|
||||||
|
return defaults.get(section, {}).get(key)
|
||||||
|
return None
|
||||||
|
|
||||||
|
val = self.conf.get(section).get(key)
|
||||||
|
default = None
|
||||||
|
if defaults and section in defaults and \
|
||||||
|
key in defaults.get(section):
|
||||||
|
default = defaults.get(section, {}).get(key)
|
||||||
|
try:
|
||||||
|
caster = self.validator.functions.get(cast)
|
||||||
|
if not caster:
|
||||||
|
try:
|
||||||
|
caster = getattr(self, cast)
|
||||||
|
except AttributeError:
|
||||||
|
self.logger.error(
|
||||||
|
"'{}': no such validator".format(cast)
|
||||||
|
)
|
||||||
|
return val
|
||||||
|
ret = caster(val)
|
||||||
|
self.logger.debug(
|
||||||
|
'[{}]:{} - found: {}, default: {}'.format(
|
||||||
|
section,
|
||||||
|
key,
|
||||||
|
val,
|
||||||
|
default
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except validate.ValidateError as exp:
|
||||||
|
self.logger.warning(
|
||||||
|
'[{}]:{} - found: {}, default: {}\n{}'.format(
|
||||||
|
section,
|
||||||
|
key,
|
||||||
|
val,
|
||||||
|
default,
|
||||||
|
str(exp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ret = default
|
||||||
|
return ret
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@ tzlocal==1.2
|
||||||
sphinxcontrib-httpdomain==1.3.0
|
sphinxcontrib-httpdomain==1.3.0
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
pyOpenSSL==16.0.0
|
pyOpenSSL==16.0.0
|
||||||
|
configobj==5.0.6
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ arrow==0.7.0
|
||||||
tzlocal==1.2
|
tzlocal==1.2
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
pyOpenSSL==16.0.0
|
pyOpenSSL==16.0.0
|
||||||
|
configobj==5.0.6
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue