add the ability to write our own config for #132

This commit is contained in:
ziirish 2016-06-15 10:10:35 +02:00
parent b38514d559
commit 25bb371032
10 changed files with 660 additions and 482 deletions

View file

@ -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:

View file

@ -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

View file

@ -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!')

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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