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 .exceptions import BUIserverException
|
||||
from .misc.backend.interface import BUIbackend
|
||||
from ._compat import ConfigParser, pickle
|
||||
from .utils import BUIlogging
|
||||
from ._compat import pickle
|
||||
from .utils import BUIlogging, BUIConfig
|
||||
|
||||
g_port = u'10000'
|
||||
g_bind = u'::'
|
||||
g_ssl = u''
|
||||
g_version = u'1'
|
||||
g_sslcert = u''
|
||||
g_sslkey = u''
|
||||
g_password = u'password'
|
||||
G_PORT = 10000
|
||||
G_BIND = u'::'
|
||||
G_SSL = False
|
||||
G_VERSION = 1
|
||||
G_SSLCERT = u''
|
||||
G_SSLKEY = u''
|
||||
G_PASSWORD = u'password'
|
||||
|
||||
DISCLOSURE = 5
|
||||
|
||||
|
|
@ -63,13 +63,18 @@ class BurpHandler(BUIbackend):
|
|||
class BUIAgent(BUIbackend, BUIlogging):
|
||||
BUIbackend.__abstractmethods__ = frozenset()
|
||||
defaults = {
|
||||
'port': g_port, 'bind': g_bind,
|
||||
'ssl': g_ssl, 'sslcert': g_sslcert, 'sslkey': g_sslkey,
|
||||
'version': g_version, 'password': g_password
|
||||
'Global': {
|
||||
'port': G_PORT,
|
||||
'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):
|
||||
self.conf = conf
|
||||
self.debug = debug
|
||||
self.padding = 1
|
||||
if level > logging.NOTSET:
|
||||
|
|
@ -102,28 +107,21 @@ class BUIAgent(BUIbackend, BUIlogging):
|
|||
handler.setLevel(lvl)
|
||||
handler.setFormatter(logging.Formatter(LOG_FORMAT))
|
||||
self.logger.addHandler(handler)
|
||||
self._logger('info', 'conf: ' + self.conf)
|
||||
self._logger('info', 'conf: ' + conf)
|
||||
self._logger('info', 'level: ' + logging.getLevelName(lvl))
|
||||
if not self.conf:
|
||||
if not conf:
|
||||
raise IOError('No configuration file found')
|
||||
|
||||
config = ConfigParser.ConfigParser(self.defaults)
|
||||
with open(self.conf) as fp:
|
||||
config.readfp(fp)
|
||||
try:
|
||||
self.port = self._safe_config_get(config.getint, 'port', 'Global', cast=int)
|
||||
self.bind = self._safe_config_get(config.get, 'bind', 'Global')
|
||||
self.vers = self._safe_config_get(config.getint, 'version', 'Global', cast=int)
|
||||
try:
|
||||
self.ssl = config.getboolean('Global', 'ssl')
|
||||
except ValueError:
|
||||
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
|
||||
# Raise exception if errors are encountered during parsing
|
||||
self.conf = BUIConfig(conf, True, self.defaults)
|
||||
self.conf.default_section('Global')
|
||||
self.port = self.conf.safe_get('port', 'integer')
|
||||
self.bind = self.conf.safe_get('bind')
|
||||
self.vers = self.conf.safe_get('version', 'integer')
|
||||
self.ssl = self.conf.safe_get('ssl', 'boolean')
|
||||
self.sslcert = self.conf.safe_get('sslcert')
|
||||
self.sslkey = self.conf.safe_get('sslkey')
|
||||
self.password = self.conf.safe_get('password')
|
||||
|
||||
self.cli = BurpHandler(self.vers, self.logger, self.conf)
|
||||
if not self.ssl:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf8 -*-
|
||||
from .interface import BUIacl, BUIaclLoader
|
||||
from ..._compat import ConfigParser
|
||||
|
||||
import json
|
||||
|
||||
|
|
@ -8,7 +7,11 @@ import json
|
|||
class ACLloader(BUIaclLoader):
|
||||
"""See :class:`burpui.misc.acl.interface.BUIaclLoader`"""
|
||||
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.admins = [
|
||||
'admin'
|
||||
|
|
@ -16,25 +19,14 @@ class ACLloader(BUIaclLoader):
|
|||
self.clients = {}
|
||||
self.servers = {}
|
||||
self.standalone = self.app.standalone
|
||||
conf = self.app.config['CFG']
|
||||
c = ConfigParser.ConfigParser()
|
||||
conf = self.app.conf
|
||||
adms = []
|
||||
with open(conf) as fp:
|
||||
c.readfp(fp)
|
||||
if c.has_section('BASIC:ACL'):
|
||||
try:
|
||||
temp = c.get('BASIC:ACL', 'admin')
|
||||
try:
|
||||
adms = json.loads(temp)
|
||||
except Exception as e:
|
||||
self.logger.error(str(e))
|
||||
adms = [temp]
|
||||
except Exception as e:
|
||||
self.logger.warning(str(e))
|
||||
for opt in c.options('BASIC:ACL'):
|
||||
if 'BASIC:ACL' in conf.options:
|
||||
adms = conf.safe_get('admin', 'force_list', section='BASIC:ACL')
|
||||
for opt in conf.options.get('BASIC:ACL').keys():
|
||||
if opt == 'admin':
|
||||
continue
|
||||
lit = c.get('BASIC:ACL', opt)
|
||||
lit = conf.safe_get(opt, section='BASIC:ACL')
|
||||
rec = []
|
||||
try:
|
||||
rec = json.loads(lit)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
# -*- coding: utf8 -*-
|
||||
import os
|
||||
|
||||
from .interface import BUIhandler, BUIuser
|
||||
from importlib import import_module
|
||||
from flask import session
|
||||
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
class UserAuthHandler(BUIhandler):
|
||||
"""See :class:`burpui.misc.auth.interface.BUIhandler`"""
|
||||
|
|
@ -17,10 +15,7 @@ class UserAuthHandler(BUIhandler):
|
|||
self.backends = []
|
||||
if self.app.auth:
|
||||
me, _ = os.path.splitext(os.path.basename(__file__))
|
||||
try:
|
||||
back = json.loads(self.app.auth)
|
||||
except:
|
||||
back = re.split(' *,+ *', self.app.auth)
|
||||
back = self.app.auth
|
||||
for au in back:
|
||||
if au == me:
|
||||
self.app.logger.error('Recursive import not permited!')
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ import json
|
|||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import codecs
|
||||
|
||||
from ast import literal_eval
|
||||
from pipes import quote
|
||||
from six import iteritems, viewkeys
|
||||
|
||||
|
|
@ -26,19 +24,19 @@ from .interface import BUIbackend
|
|||
from ..parser.burp1 import Parser
|
||||
from ...utils import human_readable as _hr, BUIcompress
|
||||
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_BURPBIN = u'/usr/sbin/burp'
|
||||
G_STRIPBIN = u'/usr/sbin/vss_strip'
|
||||
G_BURPCONFCLI = u''
|
||||
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
||||
G_TMPDIR = u'/tmp/bui'
|
||||
G_ZIP64 = u'False'
|
||||
G_INCLUDES = u'/etc/burp'
|
||||
G_ENFORCE = u'False'
|
||||
G_REVOKE = u'False'
|
||||
G_ZIP64 = False
|
||||
G_INCLUDES = [u'/etc/burp']
|
||||
G_ENFORCE = False
|
||||
G_REVOKE = False
|
||||
|
||||
|
||||
class Burp(BUIbackend):
|
||||
|
|
@ -110,8 +108,8 @@ class Burp(BUIbackend):
|
|||
and/or some global settings
|
||||
:type server: :class:`burpui.server.BUIServer`
|
||||
|
||||
:param conf: Configuration file to use
|
||||
:type conf: str
|
||||
:param conf: Configuration to use
|
||||
:type conf: :class:`burpui.utils.BUIConfig`
|
||||
|
||||
:param dummy: Does not instanciate the object (used for development
|
||||
purpose)
|
||||
|
|
@ -122,19 +120,20 @@ class Burp(BUIbackend):
|
|||
self.client_version = None
|
||||
self.server_version = None
|
||||
self.app = None
|
||||
self.zip64 = literal_eval(G_ZIP64)
|
||||
self.zip64 = G_ZIP64
|
||||
self.host = G_BURPHOST
|
||||
self.port = int(G_BURPPORT)
|
||||
self.port = G_BURPPORT
|
||||
self.burpbin = G_BURPBIN
|
||||
self.stripbin = G_STRIPBIN
|
||||
self.burpconfcli = G_BURPCONFCLI
|
||||
self.burpconfsrv = G_BURPCONFSRV
|
||||
self.tmpdir = G_TMPDIR
|
||||
self.includes = G_INCLUDES
|
||||
self.revoke = literal_eval(G_REVOKE)
|
||||
self.enforce = literal_eval(G_ENFORCE)
|
||||
self.revoke = G_REVOKE
|
||||
self.enforce = G_ENFORCE
|
||||
self.running = []
|
||||
self.defaults = {
|
||||
'Burp1': {
|
||||
'bport': G_BURPPORT,
|
||||
'bhost': G_BURPHOST,
|
||||
'burpbin': G_BURPBIN,
|
||||
|
|
@ -142,54 +141,57 @@ class Burp(BUIbackend):
|
|||
'bconfcli': G_BURPCONFCLI,
|
||||
'bconfsrv': G_BURPCONFSRV,
|
||||
'tmpdir': G_TMPDIR,
|
||||
},
|
||||
'Experimental': {
|
||||
'zip64': G_ZIP64,
|
||||
},
|
||||
'Security': {
|
||||
'includes': G_INCLUDES,
|
||||
'revoke': G_REVOKE,
|
||||
'enforce': G_ENFORCE,
|
||||
},
|
||||
}
|
||||
if conf:
|
||||
config = ConfigParser.ConfigParser(self.defaults)
|
||||
with codecs.open(conf, 'r', 'utf-8') as fileobj:
|
||||
config.readfp(fileobj)
|
||||
|
||||
self.port = self._safe_config_get(config.getint, 'bport', cast=int)
|
||||
self.host = self._safe_config_get(config.get, 'bhost')
|
||||
conf.update_defaults(self.defaults)
|
||||
conf.default_section('Burp1')
|
||||
self.port = conf.safe_get('bport', 'integer')
|
||||
self.host = conf.safe_get('bhost')
|
||||
self.burpbin = self._get_binary_path(
|
||||
config,
|
||||
conf,
|
||||
'burpbin',
|
||||
G_BURPBIN
|
||||
)
|
||||
self.stripbin = self._get_binary_path(
|
||||
config,
|
||||
conf,
|
||||
'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')
|
||||
confcli = conf.safe_get('bconfcli')
|
||||
confsrv = conf.safe_get('bconfsrv')
|
||||
tmpdir = conf.safe_get('tmpdir')
|
||||
|
||||
# Experimental options
|
||||
self.zip64 = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.zip64 = conf.safe_get(
|
||||
'zip64',
|
||||
sect='Experimental'
|
||||
'boolean',
|
||||
section='Experimental'
|
||||
)
|
||||
|
||||
# Security options
|
||||
self.includes = self._safe_config_get(
|
||||
config.get,
|
||||
self.includes = conf.safe_get(
|
||||
'includes',
|
||||
sect='Security'
|
||||
'force_list',
|
||||
section='Security'
|
||||
)
|
||||
self.enforce = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.enforce = conf.safe_get(
|
||||
'enforce',
|
||||
sect='Security'
|
||||
'boolean',
|
||||
section='Security'
|
||||
)
|
||||
self.revoke = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.revoke = conf.safe_get(
|
||||
'revoke',
|
||||
sect='Security'
|
||||
'boolean',
|
||||
section='Security'
|
||||
)
|
||||
|
||||
if tmpdir and os.path.exists(tmpdir) and not os.path.isdir(tmpdir):
|
||||
|
|
@ -268,7 +270,7 @@ class Burp(BUIbackend):
|
|||
:param default: Default value in case the retrieved value is not correct
|
||||
: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('/'):
|
||||
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)
|
||||
else:
|
||||
backup[ckey] = int(val)
|
||||
|
||||
# Needed for graphs
|
||||
if 'received' not in backup:
|
||||
backup['received'] = 1
|
||||
|
||||
return backup
|
||||
|
||||
def _parse_backup_log(self, filemap, number, client=None, agent=None):
|
||||
|
|
|
|||
|
|
@ -11,11 +11,9 @@ import re
|
|||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import codecs
|
||||
import sys
|
||||
import json
|
||||
|
||||
from ast import literal_eval
|
||||
from select import select
|
||||
from six import iteritems, viewkeys
|
||||
|
||||
|
|
@ -23,7 +21,6 @@ from .burp1 import Burp as Burp1
|
|||
from ..parser.burp2 import Parser
|
||||
from ...utils import human_readable as _hr
|
||||
from ...exceptions import BUIserverException
|
||||
from ..._compat import ConfigParser
|
||||
|
||||
if sys.version_info < (3, 3):
|
||||
TimeoutError = OSError
|
||||
|
|
@ -35,11 +32,11 @@ G_STRIPBIN = u'/usr/sbin/vss_strip'
|
|||
G_BURPCONFCLI = u'/etc/burp/burp.conf'
|
||||
G_BURPCONFSRV = u'/etc/burp/burp-server.conf'
|
||||
G_TMPDIR = u'/tmp/bui'
|
||||
G_TIMEOUT = u'5'
|
||||
G_ZIP64 = u'False'
|
||||
G_INCLUDES = u'/etc/burp'
|
||||
G_ENFORCE = u'False'
|
||||
G_REVOKE = u'False'
|
||||
G_TIMEOUT = 5
|
||||
G_ZIP64 = False
|
||||
G_INCLUDES = [u'/etc/burp']
|
||||
G_ENFORCE = False
|
||||
G_REVOKE = False
|
||||
|
||||
|
||||
# Some functions are the same as in Burp1 backend
|
||||
|
|
@ -54,102 +51,103 @@ class Burp(Burp1):
|
|||
and/or some global settings
|
||||
:type server: :class:`burpui.server.BUIServer`
|
||||
|
||||
:param conf: Configuration file to use
|
||||
:type conf: str
|
||||
:param conf: Configuration to use
|
||||
:type conf: :class:`burpui.utils.BUIConfig`
|
||||
"""
|
||||
|
||||
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.app = server
|
||||
self.client_version = None
|
||||
self.server_version = None
|
||||
self.zip64 = literal_eval(G_ZIP64)
|
||||
self.timeout = int(G_TIMEOUT)
|
||||
self.zip64 = G_ZIP64
|
||||
self.timeout = G_TIMEOUT
|
||||
self.burpbin = G_BURPBIN
|
||||
self.stripbin = G_STRIPBIN
|
||||
self.burpconfcli = G_BURPCONFCLI
|
||||
self.burpconfsrv = G_BURPCONFSRV
|
||||
self.includes = G_INCLUDES
|
||||
self.revoke = literal_eval(G_REVOKE)
|
||||
self.enforce = literal_eval(G_ENFORCE)
|
||||
self.revoke = G_REVOKE
|
||||
self.enforce = G_ENFORCE
|
||||
self.defaults = {
|
||||
'Burp2': {
|
||||
'burpbin': G_BURPBIN,
|
||||
'stripbin': G_STRIPBIN,
|
||||
'bconfcli': G_BURPCONFCLI,
|
||||
'bconfsrv': G_BURPCONFSRV,
|
||||
'timeout': G_TIMEOUT,
|
||||
'tmpdir': G_TMPDIR,
|
||||
},
|
||||
'Experimental': {
|
||||
'zip64': G_ZIP64,
|
||||
},
|
||||
'Security': {
|
||||
'includes': G_INCLUDES,
|
||||
'revoke': G_REVOKE,
|
||||
'enforce': G_ENFORCE,
|
||||
},
|
||||
}
|
||||
self.running = []
|
||||
version = ''
|
||||
if conf:
|
||||
config = ConfigParser.ConfigParser(self.defaults)
|
||||
with codecs.open(conf, 'r', 'utf-8') as conffile:
|
||||
config.readfp(conffile)
|
||||
try:
|
||||
conf.update_defaults(self.defaults)
|
||||
conf.default_section('Burp2')
|
||||
self.burpbin = self._get_binary_path(
|
||||
config,
|
||||
conf,
|
||||
'burpbin',
|
||||
G_BURPBIN,
|
||||
sect='Burp2'
|
||||
)
|
||||
self.stripbin = self._get_binary_path(
|
||||
config,
|
||||
conf,
|
||||
'stripbin',
|
||||
G_STRIPBIN,
|
||||
sect='Burp2'
|
||||
)
|
||||
confcli = self._safe_config_get(
|
||||
config.get,
|
||||
'bconfcli',
|
||||
sect='Burp2'
|
||||
confcli = conf.safe_get(
|
||||
'bconfcli'
|
||||
)
|
||||
confsrv = self._safe_config_get(
|
||||
config.get,
|
||||
'bconfsrv',
|
||||
sect='Burp2'
|
||||
confsrv = conf.safe_get(
|
||||
'bconfsrv'
|
||||
)
|
||||
self.timeout = self._safe_config_get(
|
||||
config.getint,
|
||||
self.timeout = conf.safe_get(
|
||||
'timeout',
|
||||
sect='Burp2',
|
||||
cast=int
|
||||
'integer'
|
||||
)
|
||||
tmpdir = self._safe_config_get(
|
||||
config.get,
|
||||
'tmpdir',
|
||||
sect='Burp2'
|
||||
tmpdir = conf.safe_get(
|
||||
'tmpdir'
|
||||
)
|
||||
|
||||
# Experimental options
|
||||
self.zip64 = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.zip64 = conf.safe_get(
|
||||
'zip64',
|
||||
sect='Experimental',
|
||||
cast=bool
|
||||
'boolean',
|
||||
section='Experimental'
|
||||
)
|
||||
|
||||
# Security options
|
||||
self.includes = self._safe_config_get(
|
||||
config.get,
|
||||
self.includes = conf.safe_get(
|
||||
'includes',
|
||||
sect='Security'
|
||||
'force_list',
|
||||
section='Security'
|
||||
)
|
||||
self.enforce = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.enforce = conf.safe_get(
|
||||
'enforce',
|
||||
sect='Security'
|
||||
'boolean',
|
||||
section='Security'
|
||||
)
|
||||
self.revoke = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.revoke = conf.safe_get(
|
||||
'revoke',
|
||||
sect='Security'
|
||||
'boolean',
|
||||
section='Security'
|
||||
)
|
||||
|
||||
if (tmpdir and os.path.exists(tmpdir) and
|
||||
|
|
@ -193,10 +191,6 @@ class Burp(Burp1):
|
|||
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
|
||||
# newer than BURP_MINIMAL_VERSION
|
||||
|
|
@ -576,6 +570,10 @@ class Burp(Burp1):
|
|||
if 'start' in backup and 'end' in backup:
|
||||
backup['duration'] = backup['end'] - backup['start']
|
||||
|
||||
# Needed for graphs
|
||||
if 'received' not in backup:
|
||||
backup['received'] = 1
|
||||
|
||||
return backup
|
||||
|
||||
# TODO: support old clients
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ class Parser(Doc):
|
|||
# don't check
|
||||
return True
|
||||
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):
|
||||
self.logger.warning(
|
||||
'Tried to access non-allowed path: {}'.format(path)
|
||||
|
|
@ -459,6 +459,7 @@ class Parser(Doc):
|
|||
res2[u'includes'] = parsed.flatten('include', False).keys()
|
||||
res2[u'includes_ext'] = parsed.include
|
||||
|
||||
if path in self.filecache:
|
||||
self.filecache[path]['parsed'] = res2
|
||||
|
||||
res.update(res2)
|
||||
|
|
@ -503,6 +504,7 @@ class Parser(Doc):
|
|||
res2[u'includes'] = parsed.flatten('include', False).keys()
|
||||
res2[u'includes_ext'] = parsed.include
|
||||
|
||||
if path in self.filecache:
|
||||
self.filecache[path]['parsed'] = res2
|
||||
|
||||
res.update(res2)
|
||||
|
|
|
|||
218
burpui/server.py
218
burpui/server.py
|
|
@ -13,29 +13,29 @@ import logging
|
|||
import traceback
|
||||
|
||||
from .misc.auth.handler import UserAuthHandler
|
||||
from ._compat import ConfigParser
|
||||
from .utils import BUIConfig
|
||||
from datetime import timedelta
|
||||
|
||||
from flask import Flask
|
||||
|
||||
|
||||
G_PORT = '5000'
|
||||
G_BIND = '::'
|
||||
G_REFRESH = '180'
|
||||
G_LIVEREFRESH = '5'
|
||||
G_SSL = 'False'
|
||||
G_STANDALONE = 'True'
|
||||
G_SSLCERT = ''
|
||||
G_SSLKEY = ''
|
||||
G_VERSION = '1'
|
||||
G_AUTH = 'basic'
|
||||
G_ACL = ''
|
||||
G_STORAGE = ''
|
||||
G_REDIS = ''
|
||||
G_SCOOKIE = 'False'
|
||||
G_APPSECRET = 'random'
|
||||
G_COOKIETIME = '14'
|
||||
G_PREFIX = ''
|
||||
G_PORT = 5000
|
||||
G_BIND = u'::'
|
||||
G_REFRESH = 180
|
||||
G_LIVEREFRESH = 5
|
||||
G_SSL = False
|
||||
G_STANDALONE = True
|
||||
G_SSLCERT = u''
|
||||
G_SSLKEY = u''
|
||||
G_VERSION = 1
|
||||
G_AUTH = [u'basic']
|
||||
G_ACL = u'none'
|
||||
G_STORAGE = u''
|
||||
G_REDIS = u''
|
||||
G_SCOOKIE = False
|
||||
G_APPSECRET = u'random'
|
||||
G_COOKIETIME = 14
|
||||
G_PREFIX = u''
|
||||
|
||||
|
||||
class BUIServer(Flask):
|
||||
|
|
@ -44,6 +44,34 @@ class BUIServer(Flask):
|
|||
"""
|
||||
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):
|
||||
"""The :class:`burpui.server.BUIServer` class provides the ``Burp-UI``
|
||||
server.
|
||||
|
|
@ -79,59 +107,46 @@ class BUIServer(Flask):
|
|||
if not conf:
|
||||
raise IOError('No configuration file found')
|
||||
|
||||
self.defaults = {
|
||||
'port': G_PORT,
|
||||
'bind': G_BIND,
|
||||
'refresh': G_REFRESH,
|
||||
'ssl': G_SSL,
|
||||
'sslcert': G_SSLCERT,
|
||||
'sslkey': G_SSLKEY,
|
||||
'version': G_VERSION,
|
||||
'auth': G_AUTH,
|
||||
'standalone': G_STANDALONE,
|
||||
'acl': G_ACL,
|
||||
'liverefresh': G_LIVEREFRESH,
|
||||
'storage': G_STORAGE,
|
||||
'redis': G_REDIS,
|
||||
'scookie': G_SCOOKIE,
|
||||
'appsecret': G_APPSECRET,
|
||||
'cookietime': G_COOKIETIME,
|
||||
'prefix': G_PREFIX,
|
||||
}
|
||||
config = ConfigParser.ConfigParser(self.defaults)
|
||||
with open(conf) as fp:
|
||||
config.readfp(fp)
|
||||
try:
|
||||
self.port = self._safe_config_get(
|
||||
config.getint,
|
||||
# Raise exception if errors are encountered during parsing
|
||||
self.conf = BUIConfig(conf, True, self.defaults)
|
||||
self.conf.default_section('Global')
|
||||
|
||||
self.port = self.conf.safe_get(
|
||||
'port',
|
||||
cast=int
|
||||
'integer'
|
||||
)
|
||||
self.bind = self._safe_config_get(config.get, 'bind')
|
||||
self.vers = self._safe_config_get(
|
||||
config.getint,
|
||||
self.bind = self.conf.safe_get('bind')
|
||||
self.vers = self.conf.safe_get(
|
||||
'version',
|
||||
cast=int
|
||||
'integer'
|
||||
)
|
||||
self.ssl = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.ssl = self.conf.safe_get(
|
||||
'ssl',
|
||||
cast=bool
|
||||
'boolean'
|
||||
)
|
||||
self.standalone = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.standalone = self.conf.safe_get(
|
||||
'standalone',
|
||||
cast=bool
|
||||
'boolean'
|
||||
)
|
||||
self.sslcert = self.conf.safe_get(
|
||||
'sslcert'
|
||||
)
|
||||
self.sslkey = self.conf.safe_get(
|
||||
'sslkey'
|
||||
)
|
||||
self.prefix = self.conf.safe_get(
|
||||
'prefix'
|
||||
)
|
||||
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':
|
||||
|
||||
self.auth = self.conf.safe_get(
|
||||
'auth',
|
||||
'string_lower_list'
|
||||
)
|
||||
if self.auth and 'none' not in self.auth:
|
||||
try:
|
||||
self.uhandler = UserAuthHandler(self)
|
||||
except Exception as e:
|
||||
|
|
@ -142,7 +157,9 @@ class BUIServer(Flask):
|
|||
)
|
||||
)
|
||||
raise e
|
||||
self.acl_engine = self._safe_config_get(config.get, 'acl')
|
||||
self.acl_engine = self.conf.safe_get(
|
||||
'acl'
|
||||
)
|
||||
else:
|
||||
self.config['LOGIN_DISABLED'] = True
|
||||
# No login => no ACL
|
||||
|
|
@ -182,56 +199,46 @@ class BUIServer(Flask):
|
|||
self.acl = False
|
||||
|
||||
# UI options
|
||||
self.config['REFRESH'] = self._safe_config_get(
|
||||
config.getint,
|
||||
self.config['REFRESH'] = self.conf.safe_get(
|
||||
'refresh',
|
||||
'UI',
|
||||
cast=int
|
||||
'integer',
|
||||
'UI'
|
||||
)
|
||||
self.config['LIVEREFRESH'] = self._safe_config_get(
|
||||
config.getint,
|
||||
self.config['LIVEREFRESH'] = self.conf.safe_get(
|
||||
'liverefresh',
|
||||
'UI',
|
||||
cast=int
|
||||
'integer',
|
||||
'UI'
|
||||
)
|
||||
|
||||
# Production options
|
||||
self.storage = self._safe_config_get(
|
||||
config.get,
|
||||
self.storage = self.conf.safe_get(
|
||||
'storage',
|
||||
'Production'
|
||||
section='Production'
|
||||
)
|
||||
self.redis = self._safe_config_get(
|
||||
config.get,
|
||||
self.redis = self.conf.safe_get(
|
||||
'redis',
|
||||
'Production'
|
||||
section='Production'
|
||||
)
|
||||
|
||||
# Security options
|
||||
self.scookie = self._safe_config_get(
|
||||
config.getboolean,
|
||||
self.scookie = self.conf.safe_get(
|
||||
'scookie',
|
||||
'Security',
|
||||
cast=bool
|
||||
'boolean',
|
||||
section='Security'
|
||||
)
|
||||
self.config['SECRET_KEY'] = self._safe_config_get(
|
||||
config.get,
|
||||
self.config['SECRET_KEY'] = self.conf.safe_get(
|
||||
'appsecret',
|
||||
'Security'
|
||||
section='Security'
|
||||
)
|
||||
self.config['REMEMBER_COOKIE_DURATION'] = \
|
||||
self.config['PERMANENT_SESSION_LIFETIME'] = timedelta(
|
||||
days=self._safe_config_get(
|
||||
config.getint,
|
||||
days=self.conf.safe_get(
|
||||
'cookietime',
|
||||
'Security',
|
||||
cast=int
|
||||
'integer',
|
||||
section='Security'
|
||||
)
|
||||
)
|
||||
|
||||
except ConfigParser.NoOptionError as e:
|
||||
self.logger.error(str(e))
|
||||
|
||||
self.config['STANDALONE'] = self.standalone
|
||||
|
||||
self.logger.info('burp version: {}'.format(self.vers))
|
||||
|
|
@ -265,7 +272,7 @@ class BUIServer(Flask):
|
|||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
mod = __import__(module, fromlist=['Burp'])
|
||||
Client = mod.Burp
|
||||
self.cli = Client(self, conf=conf)
|
||||
self.cli = Client(self, conf=self.conf)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
self.logger.critical(
|
||||
|
|
@ -278,39 +285,6 @@ class BUIServer(Flask):
|
|||
|
||||
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):
|
||||
"""The :func:`burpui.server.BUIServer.manual_run` functions is used to
|
||||
actually launch the ``Burp-UI`` server.
|
||||
|
|
|
|||
210
burpui/utils.py
210
burpui/utils.py
|
|
@ -8,18 +8,26 @@
|
|||
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
import string
|
||||
import sys
|
||||
import codecs
|
||||
import json
|
||||
import shutil
|
||||
import zipfile
|
||||
import tarfile
|
||||
import logging
|
||||
import configobj
|
||||
import validate
|
||||
|
||||
from inspect import currentframe, getouterframes
|
||||
from ._compat import PY3
|
||||
from . import __version__, __release__
|
||||
|
||||
if PY3:
|
||||
long = int # pragma: no cover
|
||||
basestring = str # pragma: no cover
|
||||
|
||||
|
||||
class human_readable(long):
|
||||
|
|
@ -273,3 +281,205 @@ class ReverseProxied(object):
|
|||
self.app.warning("'prefix' must start with a '/'!")
|
||||
|
||||
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
|
||||
six==1.10.0
|
||||
pyOpenSSL==16.0.0
|
||||
configobj==5.0.6
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ arrow==0.7.0
|
|||
tzlocal==1.2
|
||||
six==1.10.0
|
||||
pyOpenSSL==16.0.0
|
||||
configobj==5.0.6
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue