mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-15 14:16:08 -06:00
1443 lines
44 KiB
Python
1443 lines
44 KiB
Python
# -*- coding: utf8 -*-
|
|
"""
|
|
Burp-UI is a web-ui for burp backup written in python with Flask and
|
|
jQuery/Bootstrap
|
|
|
|
.. module:: burpui.cli
|
|
:platform: Unix
|
|
:synopsis: Burp-UI CLI module.
|
|
|
|
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import click
|
|
|
|
if os.getenv("BUI_MODE") in ["server", "ws"] or "websocket" in sys.argv:
|
|
try:
|
|
from gevent import monkey
|
|
|
|
monkey.patch_socket()
|
|
except ImportError:
|
|
pass
|
|
|
|
from .app import create_app # noqa
|
|
from .exceptions import BUIserverException # noqa
|
|
|
|
try:
|
|
from flask_socketio import SocketIO # noqa
|
|
|
|
WS_AVAILABLE = True
|
|
except ImportError:
|
|
WS_AVAILABLE = False
|
|
|
|
ROOT = os.path.dirname(os.path.realpath(__file__))
|
|
DEBUG = os.getenv("BUI_DEBUG") or os.getenv("FLASK_DEBUG") or False
|
|
DEBUG = DEBUG and DEBUG.lower() not in ["false", "no", "0"]
|
|
|
|
VERBOSE = os.getenv("BUI_VERBOSE") or 0
|
|
if VERBOSE:
|
|
try:
|
|
VERBOSE = int(VERBOSE)
|
|
except ValueError:
|
|
VERBOSE = 0
|
|
|
|
# UNITTEST is used to skip the burp-2 requirements for modes != server
|
|
UNITTEST = os.getenv("BUI_MODE") not in ["server", "manage", "celery", "legacy", "ws"]
|
|
CLI = os.getenv("BUI_MODE") not in ["server", "legacy"]
|
|
|
|
app = create_app(
|
|
conf=os.environ.get("BUI_CONFIG"),
|
|
verbose=VERBOSE,
|
|
logfile=os.environ.get("BUI_LOGFILE"),
|
|
debug=DEBUG,
|
|
gunicorn=False,
|
|
unittest=UNITTEST,
|
|
cli=CLI,
|
|
websocket_server=(os.getenv("BUI_MODE") == "ws" or "websocket" in sys.argv),
|
|
)
|
|
|
|
try:
|
|
from flask_migrate import Migrate
|
|
|
|
from .ext.sql import db
|
|
from .extensions import create_db
|
|
|
|
# This may have been reseted by create_app
|
|
if isinstance(app.database, bool):
|
|
app.config["WITH_SQL"] = app.database
|
|
else:
|
|
app.config["WITH_SQL"] = app.database and app.database.lower() != "none"
|
|
|
|
if app.config["WITH_SQL"]:
|
|
create_db(app, True)
|
|
|
|
mig_dir = os.getenv("BUI_MIGRATIONS")
|
|
if mig_dir:
|
|
migrate = Migrate(app, db, mig_dir)
|
|
else:
|
|
migrate = Migrate(app, db)
|
|
except ImportError:
|
|
pass
|
|
|
|
# Utilities functions
|
|
|
|
|
|
def _die(error, appli=None):
|
|
appli = " '{}'".format(appli) if appli else ""
|
|
err(
|
|
"Unable to initialize the application{}: {}".format(appli, error),
|
|
)
|
|
sys.exit(2)
|
|
|
|
|
|
def _log(message, *args, color=None, err=False):
|
|
msg = message % args if args else message
|
|
if color is not None:
|
|
msg = click.style(msg, fg=color)
|
|
click.echo(msg, err=err)
|
|
|
|
|
|
def log(message="", *args):
|
|
_log(message, *args)
|
|
|
|
|
|
def ok(message="", *args):
|
|
_log(message, *args, color="green")
|
|
|
|
|
|
def info(message="", *args):
|
|
_log(message, *args, color="blue")
|
|
|
|
|
|
def warn(message="", *args):
|
|
_log(message, *args, color="yellow")
|
|
|
|
|
|
def err(message="", *args, err=True):
|
|
_log(message, *args, color="red", err=err)
|
|
|
|
|
|
@app.cli.command()
|
|
def legacy():
|
|
"""Legacy server for backward compatibility."""
|
|
warn(
|
|
"If you want to pass options, you should run 'python -m burpui "
|
|
"-m legacy [...]' instead",
|
|
)
|
|
app.manual_run()
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option(
|
|
"-b",
|
|
"--bind",
|
|
default="127.0.0.1",
|
|
help="Which address to bind to for the websocket server",
|
|
)
|
|
@click.option(
|
|
"-p",
|
|
"--port",
|
|
default=5001,
|
|
help="Which port to listen on for the websocket server",
|
|
)
|
|
@click.option(
|
|
"-d",
|
|
"--debug",
|
|
default=False,
|
|
is_flag=True,
|
|
help="Whether to start the websocket server in debug mode",
|
|
)
|
|
def websocket(bind, port, debug):
|
|
"""Start a new websocket server."""
|
|
try:
|
|
from .ext.ws import socketio
|
|
except ImportError:
|
|
_die(
|
|
"Missing requirement, did you ran 'pip install" ' "burp-ui[websocket]"\'?',
|
|
"websocket",
|
|
)
|
|
socketio.run(app, host=bind, port=port, debug=debug)
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option(
|
|
"-b", "--backend", default="BASIC", help="User Backend (default is BASIC)."
|
|
)
|
|
@click.option("-p", "--password", help="Password to assign to user.", default=None)
|
|
@click.option(
|
|
"-a",
|
|
"--ask",
|
|
default=False,
|
|
is_flag=True,
|
|
help="If no password is provided and this flag is enabled, "
|
|
"you'll be prompted for one, else a random one will be "
|
|
"generated.",
|
|
)
|
|
@click.option(
|
|
"-v", "--verbose", default=False, is_flag=True, help="Add extra debug messages."
|
|
)
|
|
@click.argument("name")
|
|
def create_user(backend, password, ask, verbose, name):
|
|
"""Create a new user."""
|
|
try:
|
|
msg = app.load_modules()
|
|
except Exception as e:
|
|
msg = str(e)
|
|
|
|
if not backend.endswith(":AUTH"):
|
|
backend = f"{backend}:AUTH"
|
|
backend = backend.upper()
|
|
|
|
if msg:
|
|
_die(msg, "create_user")
|
|
|
|
info("[*] Adding '{}' user...".format(name))
|
|
try:
|
|
handler = getattr(app, "uhandler")
|
|
except AttributeError:
|
|
handler = None
|
|
|
|
if not handler or len(handler.backends) == 0 or backend not in handler.backends:
|
|
err("[!] No authentication backend found")
|
|
sys.exit(1)
|
|
|
|
back = handler.backends[backend]
|
|
|
|
if back.add_user is False:
|
|
err("[!] The '{}' backend does not support user creation".format(backend))
|
|
sys.exit(2)
|
|
|
|
if not password:
|
|
if ask:
|
|
import getpass
|
|
|
|
password = getpass.getpass()
|
|
confirm = getpass.getpass("Confirm: ")
|
|
if password != confirm:
|
|
err("[!] Passwords mismatch")
|
|
sys.exit(3)
|
|
else:
|
|
import random
|
|
|
|
alphabet = (
|
|
"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLM" "NOPQRSTUVWXYZ"
|
|
)
|
|
pw_length = 8
|
|
mypw = ""
|
|
|
|
for i in range(pw_length):
|
|
next_index = random.randrange(len(alphabet))
|
|
mypw += alphabet[next_index]
|
|
password = mypw
|
|
info("[+] Generated password: {}".format(password))
|
|
|
|
success, message, _ = back.add_user(name, password)
|
|
_log(
|
|
"[+] Success: {}{}".format(
|
|
success, " -> {}".format(message) if verbose and message else ""
|
|
),
|
|
color="green" if success else "red",
|
|
)
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option("-p", "--password", help="Password to assign to user.", default=None)
|
|
@click.option(
|
|
"-u",
|
|
"--username",
|
|
help="Provide the username to get the full " "configuration line.",
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"-b",
|
|
"--batch",
|
|
default=False,
|
|
is_flag=True,
|
|
help="Don't be extra verbose so that you can use the output "
|
|
"directly in your scripts. Requires both -u and -p.",
|
|
)
|
|
def hash_password(password, username, batch):
|
|
"""Hash a given password to fill the configuration file."""
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
if batch and (not username or not password):
|
|
err(
|
|
"You need to provide both a username and a password using the "
|
|
"-u and -p flags!",
|
|
)
|
|
sys.exit(1)
|
|
|
|
askpass = False
|
|
if not password:
|
|
askpass = True
|
|
import getpass
|
|
|
|
password = getpass.getpass()
|
|
|
|
hashed = generate_password_hash(password)
|
|
if not batch:
|
|
log("'{}' hashed into: {}".format(password if not askpass else "*" * 8, hashed))
|
|
if username:
|
|
if not batch:
|
|
info("#8<{}".format("-" * 77))
|
|
log("{} = {}".format(username, hashed))
|
|
if not batch:
|
|
info("#8<{}".format("-" * 77))
|
|
|
|
|
|
@app.cli.command()
|
|
@click.argument("language")
|
|
def init_translation(language):
|
|
"""Initialize a new translation for the given language."""
|
|
try:
|
|
import babel # noqa
|
|
except ImportError:
|
|
warn("Missing i18n requirements, giving up")
|
|
return
|
|
os.chdir(os.path.join(ROOT, ".."))
|
|
os.system(
|
|
"pybabel extract -F babel.cfg -k __ -k lazy_gettext -o messages.pot burpui"
|
|
)
|
|
os.system(
|
|
"pybabel init -i messages.pot -d burpui/translations -l {}".format(language)
|
|
)
|
|
os.unlink("messages.pot")
|
|
|
|
|
|
@app.cli.command()
|
|
def update_translation():
|
|
"""Update translation files."""
|
|
try:
|
|
import babel # noqa
|
|
except ImportError:
|
|
warn("Missing i18n requirements, giving up")
|
|
return
|
|
os.chdir(os.path.join(ROOT, ".."))
|
|
os.system(
|
|
"pybabel extract -F babel.cfg -k __ -k lazy_gettext -o messages.pot burpui"
|
|
)
|
|
os.system("pybabel update -i messages.pot -d burpui/translations")
|
|
os.unlink("messages.pot")
|
|
|
|
|
|
@app.cli.command()
|
|
def compile_translation():
|
|
"""Compile translations."""
|
|
try:
|
|
import babel # noqa
|
|
except ImportError:
|
|
warn("Missing i18n requirements, giving up")
|
|
return
|
|
os.chdir(os.path.join(ROOT, ".."))
|
|
os.system("pybabel compile -f -d burpui/translations")
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option(
|
|
"-b",
|
|
"--burp-conf-cli",
|
|
"bconfcli",
|
|
default=None,
|
|
help="Burp client configuration file",
|
|
)
|
|
@click.option(
|
|
"-s",
|
|
"--burp-conf-serv",
|
|
"bconfsrv",
|
|
default=None,
|
|
help="Burp server configuration file",
|
|
)
|
|
@click.option(
|
|
"-c",
|
|
"--client",
|
|
default="bui",
|
|
help="Name of the burp client that will be used by Burp-UI " '(defaults to "bui")',
|
|
)
|
|
@click.option(
|
|
"-l",
|
|
"--listen",
|
|
default="0.0.0.0:5971",
|
|
help="Setup a custom listen port for the Burp-UI restorations",
|
|
)
|
|
@click.option(
|
|
"-h",
|
|
"--host",
|
|
default="::1",
|
|
help='Address of the status server (defaults to "::1")',
|
|
)
|
|
@click.option("-r", "--redis", default=None, help="Redis URL to connect to")
|
|
@click.option(
|
|
"-d",
|
|
"--database",
|
|
default=None,
|
|
help="Database to connect to for persistent storage",
|
|
)
|
|
@click.option("-p", "--plugins", default=None, help="Plugins location")
|
|
@click.option("-m", "--monitor", default=None, help="bui-monitor configuration file")
|
|
@click.option(
|
|
"-i", "--monitor-listen", "mbind", default=None, help="bui-monitor bind address"
|
|
)
|
|
@click.option(
|
|
"-C",
|
|
"--concurrency",
|
|
default=None,
|
|
type=click.INT,
|
|
help="Number of concurrent requests addressed to the monitor",
|
|
)
|
|
@click.option(
|
|
"-P",
|
|
"--pool-size",
|
|
"pool",
|
|
default=None,
|
|
type=click.INT,
|
|
help="Number of burp-client processes to spawn in the monitor",
|
|
)
|
|
@click.option(
|
|
"-B",
|
|
"--backend",
|
|
default=None,
|
|
help="Switch to another backend",
|
|
type=click.Choice(["burp2", "parallel"]),
|
|
)
|
|
@click.option(
|
|
"-a",
|
|
"--assume-version",
|
|
"assume",
|
|
default=None,
|
|
help="If we cannot determine server version use this one",
|
|
)
|
|
@click.option(
|
|
"-n",
|
|
"--dry",
|
|
is_flag=True,
|
|
help="Dry mode. Do not edit the files but display changes",
|
|
)
|
|
def setup_burp(
|
|
bconfcli,
|
|
bconfsrv,
|
|
client,
|
|
listen,
|
|
host,
|
|
redis,
|
|
database,
|
|
plugins,
|
|
monitor,
|
|
mbind,
|
|
concurrency,
|
|
pool,
|
|
backend,
|
|
assume,
|
|
dry,
|
|
):
|
|
"""Setup burp client for burp-ui."""
|
|
if app.config["BACKEND"] not in ["burp2", "parallel"] and not backend:
|
|
err("Sorry, you can only setup the 'burp2' and the 'parallel' backends")
|
|
sys.exit(1)
|
|
|
|
if not app.config["STANDALONE"]:
|
|
err("Sorry, only the standalone mode is supported")
|
|
sys.exit(1)
|
|
|
|
if concurrency:
|
|
from multiprocessing import cpu_count
|
|
|
|
if concurrency > cpu_count():
|
|
warn(
|
|
"Warning: setting a concurrency level higher than the available CPU"
|
|
" count might cause you some troubles"
|
|
)
|
|
|
|
if pool and concurrency:
|
|
if concurrency > pool:
|
|
warn(
|
|
"Warning: setting a concurrency level higher than the available"
|
|
" processes in the monitor is not recommended"
|
|
)
|
|
|
|
is_parallel = app.config["BACKEND"] == "parallel" or (
|
|
backend and backend == "parallel"
|
|
)
|
|
|
|
# enforce burp2 backend for the configuration
|
|
app.config["BACKEND"] = "burp2"
|
|
|
|
try:
|
|
msg = app.load_modules()
|
|
except Exception as e:
|
|
msg = str(e)
|
|
|
|
if msg:
|
|
_die(msg, "setup-burp")
|
|
|
|
import difflib
|
|
import tempfile
|
|
|
|
from .app import get_redis_server
|
|
from .config import BUIConfig
|
|
from .misc.backend.utils.constant import BURP_BIND_MULTIPLE, BURP_LISTEN_OPTION
|
|
from .misc.parser.utils import Config
|
|
|
|
if monitor:
|
|
monconf = BUIConfig(monitor)
|
|
monconf_orig = []
|
|
mon_orig = mon_source = monitor
|
|
|
|
if dry:
|
|
try:
|
|
with open(monitor) as fil:
|
|
monconf_orig = fil.readlines()
|
|
except:
|
|
pass
|
|
|
|
(_, temp) = tempfile.mkstemp()
|
|
monconf.options.filename = temp
|
|
|
|
parser = app.client.get_parser()
|
|
server_version = app.client.get_server_version() or assume
|
|
orig = source = None
|
|
conf_orig = []
|
|
if dry:
|
|
try:
|
|
with open(app.conf.options.filename) as fil:
|
|
conf_orig = fil.readlines()
|
|
except:
|
|
pass
|
|
|
|
orig = source = app.conf.options.filename
|
|
(_, temp) = tempfile.mkstemp()
|
|
app.conf.options.filename = temp
|
|
|
|
# handle migration of old config files
|
|
if app.conf.section_exists("Burp2"):
|
|
if app.conf.rename_section("Burp2", "Burp", source):
|
|
info("Renaming old [Burp2] section")
|
|
app.conf._refresh(True)
|
|
|
|
refresh = False
|
|
if not app.conf.lookup_section("Burp", source):
|
|
refresh = True
|
|
if not app.conf.lookup_section("Global", source):
|
|
refresh = True
|
|
if (database or redis) and not app.conf.lookup_section("Production", source):
|
|
refresh = True
|
|
if concurrency and not app.conf.lookup_section("Parallel", source):
|
|
refresh = True
|
|
|
|
if refresh:
|
|
app.conf._refresh(True)
|
|
|
|
if monitor and not monconf.lookup_section("Global", mon_source):
|
|
monconf._refresh(True)
|
|
|
|
def _edit_conf(key, val, attr, section="Burp", obj=app.client, conf=app.conf):
|
|
if val and (
|
|
(key not in conf.options[section])
|
|
or (key in conf.options[section] and val != conf.options[section][key])
|
|
):
|
|
adding = key not in conf.options[section]
|
|
conf.options[section][key] = val
|
|
conf.options.write()
|
|
if obj:
|
|
setattr(obj, attr, val)
|
|
if adding:
|
|
msg = f'Adding new option "{key}={val}" to section [{section}] in {conf.conffile}'
|
|
else:
|
|
msg = f'Updating option "{key}={val}" in section [{section}] in {conf.conffile}'
|
|
info(msg)
|
|
return True
|
|
return False
|
|
|
|
def _color_diff(line):
|
|
if line.startswith("+"):
|
|
return click.style(line, fg="green")
|
|
elif line.startswith("-"):
|
|
return click.style(line, fg="red")
|
|
elif line.startswith("^"):
|
|
return click.style(line, fg="blue")
|
|
return line
|
|
|
|
refresh = False
|
|
refresh |= _edit_conf("bconfcli", bconfcli, "burpconfcli")
|
|
refresh |= _edit_conf("bconfsrv", bconfsrv, "burpconfsrv")
|
|
refresh |= _edit_conf("plugins", plugins, "plugins", "Global", app)
|
|
if backend:
|
|
refresh |= _edit_conf("backend", backend, None, "Global", None)
|
|
if is_parallel and concurrency:
|
|
refresh |= _edit_conf("concurrency", concurrency, None, "Parallel", None)
|
|
if mbind:
|
|
refresh |= _edit_conf("host", mbind, None, "Parallel", None)
|
|
|
|
if refresh:
|
|
app.conf._refresh(True)
|
|
|
|
refresh = False
|
|
if monitor and pool:
|
|
refresh |= _edit_conf("pool", pool, None, "Global", None, monconf)
|
|
if monitor and mbind:
|
|
refresh |= _edit_conf("bind", mbind, None, "Global", None, monconf)
|
|
if monitor:
|
|
refresh |= _edit_conf("bconfcli", bconfcli, None, "Burp", None, monconf)
|
|
|
|
if refresh:
|
|
monconf._refresh(True)
|
|
|
|
if monitor and app.config["BACKEND"] == "parallel":
|
|
mon_password = monconf.options["Global"].get("password")
|
|
back_password = app.conf.options["Parallel"].get("password")
|
|
|
|
if mon_password != back_password:
|
|
click.echo(
|
|
click.style(
|
|
"Backend password does not match monitor password", fg="yellow"
|
|
)
|
|
)
|
|
|
|
if redis:
|
|
try:
|
|
# detect missing modules
|
|
import socket
|
|
|
|
import celery # noqa
|
|
import redis as redis_client # noqa
|
|
|
|
if (
|
|
"redis" not in app.conf.options["Production"]
|
|
or "redis" in app.conf.options["Production"]
|
|
and app.conf.options["Production"]["redis"] != redis
|
|
) and app.redis != redis:
|
|
app.conf.options["Production"]["redis"] = redis
|
|
app.redis = redis
|
|
|
|
rhost, rport, _ = get_redis_server(app)
|
|
ret = -1
|
|
for _ in range(10):
|
|
if ret == 0:
|
|
break
|
|
|
|
for res in socket.getaddrinfo(
|
|
rhost, rport, socket.AF_UNSPEC, socket.SOCK_STREAM
|
|
):
|
|
if ret == 0:
|
|
break
|
|
af, socktype, proto, _, sa = res
|
|
try:
|
|
s = socket.socket(af, socktype, proto)
|
|
except socket.error:
|
|
continue
|
|
try:
|
|
ret = s.connect_ex(sa)
|
|
except:
|
|
continue
|
|
|
|
time.sleep(1)
|
|
|
|
if ret == 0:
|
|
app.conf.options["Production"]["celery"] = "true"
|
|
|
|
app.conf.options["Production"]["storage"] = "redis"
|
|
|
|
app.conf.options["Production"]["cache"] = "redis"
|
|
else:
|
|
warn("Unable to contact the redis server, disabling it")
|
|
app.conf.options["Production"]["storage"] = "default"
|
|
app.conf.options["Production"]["cache"] = "default"
|
|
if app.use_celery:
|
|
app.conf.options["Production"]["celery"] = "false"
|
|
|
|
app.conf.options.write()
|
|
app.conf._refresh(True)
|
|
except ImportError:
|
|
warn(
|
|
"Unable to activate redis & celery. Did you ran the "
|
|
"'pip install burp-ui[celery]' and "
|
|
"'pip install burp-ui[gunicorn-extra]' commands first?"
|
|
)
|
|
|
|
if database:
|
|
try:
|
|
from .ext.sql import db # noqa
|
|
|
|
if (
|
|
"database" not in app.conf.options["Production"]
|
|
or "database" in app.conf.options["Production"]
|
|
and app.conf.options["Production"]["database"] != database
|
|
) and app.database != database:
|
|
app.conf.options["Production"]["database"] = database
|
|
app.conf.options.write()
|
|
app.conf._refresh(True)
|
|
except ImportError:
|
|
warn(
|
|
"It looks like some dependencies are missing. Did you ran "
|
|
"the 'pip install \"burp-ui[sql]\"' command first?"
|
|
)
|
|
|
|
if dry:
|
|
temp = app.conf.options.filename
|
|
app.conf.options.filename = orig
|
|
after = []
|
|
try:
|
|
if not os.path.exists(temp) or os.path.getsize(temp) == 0:
|
|
after = conf_orig
|
|
else:
|
|
with open(temp) as fil:
|
|
after = fil.readlines()
|
|
os.unlink(temp)
|
|
except:
|
|
pass
|
|
diff = difflib.unified_diff(
|
|
conf_orig, after, fromfile=orig, tofile="{}.new".format(orig)
|
|
)
|
|
out = ""
|
|
for line in diff:
|
|
out += _color_diff(line)
|
|
if out:
|
|
click.echo_via_pager(out)
|
|
|
|
if dry and monitor:
|
|
temp = monconf.options.filename
|
|
monconf.options.filename = mon_orig
|
|
after = []
|
|
try:
|
|
if not os.path.exists(temp) or os.path.getsize(temp) == 0:
|
|
after = monconf_orig
|
|
else:
|
|
with open(temp) as fil:
|
|
after = fil.readlines()
|
|
os.unlink(temp)
|
|
except:
|
|
pass
|
|
diff = difflib.unified_diff(
|
|
monconf_orig, after, fromfile=mon_orig, tofile="{}.new".format(mon_orig)
|
|
)
|
|
out = ""
|
|
for line in diff:
|
|
out += _color_diff(line)
|
|
if out:
|
|
click.echo_via_pager(out)
|
|
|
|
bconfcli = (
|
|
bconfcli
|
|
or app.conf.options["Burp"].get("bconfcli")
|
|
or getattr(app.client, "burpconfcli")
|
|
)
|
|
bconfsrv = (
|
|
bconfsrv
|
|
or app.conf.options["Burp"].get("bconfsrv")
|
|
or getattr(app.client, "burpconfsrv")
|
|
)
|
|
dest_bconfcli = bconfcli
|
|
|
|
is_burp_2_2_10_plus = False
|
|
listen_opt = "status_address"
|
|
if server_version and server_version >= BURP_LISTEN_OPTION:
|
|
is_burp_2_2_10_plus = True
|
|
listen_opt = "listen_status"
|
|
|
|
_, restore_port = listen.split(":")
|
|
|
|
if not os.path.exists(bconfcli):
|
|
clitpl = f"""
|
|
mode = client
|
|
port = {restore_port}
|
|
status_port = 4972
|
|
server = ::1
|
|
password = abcdefgh
|
|
cname = {client}
|
|
protocol = 1
|
|
pidfile = /tmp/burp.client.pid
|
|
syslog = 0
|
|
stdout = 1
|
|
progress_counter = 1
|
|
network_timeout = 72000
|
|
server_can_restore = 0
|
|
cross_all_filesystems=0
|
|
ca_burp_ca = /usr/sbin/burp_ca
|
|
ca_csr_dir = /etc/burp/CA-client
|
|
ssl_cert_ca = /etc/burp/ssl_cert_ca-client-{client}.pem
|
|
ssl_cert = /etc/burp/ssl_cert-bui-client.pem
|
|
ssl_key = /etc/burp/ssl_cert-bui-client.key
|
|
ssl_key_password = password
|
|
ssl_peer_cn = burpserver
|
|
include = /home
|
|
exclude_fs = sysfs
|
|
exclude_fs = tmpfs
|
|
nobackup = .nobackup
|
|
exclude_comp=bz2
|
|
exclude_comp=gz
|
|
"""
|
|
|
|
if dry:
|
|
(_, dest_bconfcli) = tempfile.mkstemp()
|
|
with open(dest_bconfcli, "w") as confcli:
|
|
confcli.write(clitpl)
|
|
|
|
parser = app.client.get_parser(assume)
|
|
|
|
confcli = Config(dest_bconfcli, parser, "srv")
|
|
confcli.set_default(dest_bconfcli)
|
|
confcli.parse()
|
|
|
|
if confcli.get("cname") != client:
|
|
confcli["cname"] = client
|
|
if confcli.get("server") != host:
|
|
confcli["server"] = host
|
|
c_status_port = (
|
|
confcli.get("status_port", [4972])[0]
|
|
if confcli.version >= BURP_BIND_MULTIPLE
|
|
else confcli.get("status_port", 4972)
|
|
)
|
|
c_server_port = (
|
|
confcli.get("port", [4971])[0]
|
|
if confcli.version >= BURP_BIND_MULTIPLE
|
|
else confcli.get("port", 4971)
|
|
)
|
|
if c_server_port != restore_port:
|
|
confcli["port"] = [restore_port]
|
|
|
|
if confcli.dirty:
|
|
if dry:
|
|
(_, dstfile) = tempfile.mkstemp()
|
|
else:
|
|
dstfile = bconfcli
|
|
|
|
confcli.store(conf=bconfcli, dest=dstfile, insecure=True)
|
|
if dry:
|
|
before = []
|
|
after = []
|
|
try:
|
|
with open(bconfcli) as fil:
|
|
before = fil.readlines()
|
|
except:
|
|
pass
|
|
try:
|
|
with open(dstfile) as fil:
|
|
after = fil.readlines()
|
|
os.unlink(dstfile)
|
|
except:
|
|
pass
|
|
|
|
if dest_bconfcli != bconfcli:
|
|
# the file did not exist
|
|
os.unlink(dest_bconfcli)
|
|
before = []
|
|
|
|
diff = difflib.unified_diff(
|
|
before, after, fromfile=bconfcli, tofile="{}.new".format(bconfcli)
|
|
)
|
|
out = ""
|
|
for line in diff:
|
|
out += _color_diff(line)
|
|
if out:
|
|
click.echo_via_pager(out)
|
|
|
|
if not os.path.exists(bconfsrv):
|
|
err("Unable to locate burp-server configuration, aborting!")
|
|
sys.exit(1)
|
|
|
|
confsrv = Config(bconfsrv, parser, "srv")
|
|
confsrv.set_default(bconfsrv)
|
|
confsrv.parse()
|
|
|
|
bind_index = -1
|
|
|
|
if host not in ["::1", "127.0.0.1"]:
|
|
bind = confsrv.get(listen_opt)
|
|
if is_burp_2_2_10_plus:
|
|
bind_list = list(bind)
|
|
for idx, line in enumerate(bind_list):
|
|
if line.endswith(":{}".format(c_status_port)):
|
|
bind = line.split(":")[0]
|
|
bind_index = idx
|
|
break
|
|
else:
|
|
warn(
|
|
"Unable to locate a 'listen_status' associated to your client "
|
|
"'status_port' ({})".format(c_status_port)
|
|
)
|
|
if (bind and bind not in [host, "::", "0.0.0.0"]) or not bind:
|
|
warn(
|
|
"It looks like your burp server is not exposing it's "
|
|
"status port in a way that is reachable by Burp-UI!"
|
|
)
|
|
info(
|
|
"You may want to set the '{0}' setting with "
|
|
"either '{1}{3}', '::{3}' or '0.0.0.0{3}' in the {2} file "
|
|
"in order to make Burp-UI work".format(
|
|
listen_opt,
|
|
host,
|
|
bconfsrv,
|
|
":{}".format(c_status_port) if is_burp_2_2_10_plus else "",
|
|
)
|
|
)
|
|
|
|
MAX_STATUS_CHILDREN = pool if pool is not None else 15
|
|
if not is_burp_2_2_10_plus:
|
|
s_port = confsrv.get("port", [4971])
|
|
if restore_port not in s_port:
|
|
confsrv["port"] = restore_port
|
|
else:
|
|
s_listen = confsrv.get("listen", [])
|
|
if listen not in s_listen:
|
|
confsrv["listen"] = listen
|
|
status_port = confsrv.get("status_port", [4972])
|
|
do_warn = False
|
|
if "max_status_children" not in confsrv:
|
|
info(
|
|
"We need to set the number of 'max_status_children'. "
|
|
"Setting it to {}.".format(MAX_STATUS_CHILDREN)
|
|
)
|
|
confsrv["max_status_children"] = MAX_STATUS_CHILDREN
|
|
elif confsrv.version and confsrv.version < BURP_BIND_MULTIPLE:
|
|
max_status_children = confsrv.get("max_status_children")
|
|
if not max_status_children or max_status_children < MAX_STATUS_CHILDREN:
|
|
confsrv["max_status_children"] = MAX_STATUS_CHILDREN
|
|
do_warn = True
|
|
else:
|
|
max_status_children = confsrv.get("max_status_children", [])
|
|
if not is_burp_2_2_10_plus:
|
|
for idx, value in enumerate(status_port):
|
|
if value == c_status_port:
|
|
bind_index = idx
|
|
break
|
|
if bind_index >= 0 and len(max_status_children) >= bind_index:
|
|
if max_status_children[bind_index] < MAX_STATUS_CHILDREN:
|
|
confsrv["max_status_children"][bind_index] = MAX_STATUS_CHILDREN
|
|
do_warn = True
|
|
else:
|
|
if max_status_children[-1] < MAX_STATUS_CHILDREN:
|
|
confsrv["max_status_children"][-1] = MAX_STATUS_CHILDREN
|
|
do_warn = True
|
|
|
|
if do_warn:
|
|
warn(
|
|
"We need to raise the number of 'max_status_children' to {}.".format(
|
|
MAX_STATUS_CHILDREN
|
|
)
|
|
)
|
|
|
|
if "restore_client" not in confsrv:
|
|
confsrv["restore_client"] = client
|
|
else:
|
|
restore = confsrv.getlist("restore_client")
|
|
if client not in restore:
|
|
confsrv["restore_client"].append(client)
|
|
|
|
confsrv["monitor_browse_cache"] = True
|
|
|
|
ca_client_dir = confsrv.get("ca_csr_dir")
|
|
if ca_client_dir and not os.path.exists(ca_client_dir):
|
|
try:
|
|
os.makedirs(ca_client_dir)
|
|
except IOError as exp:
|
|
_log(
|
|
'Unable to create "{}" dir: {}'.format(ca_client_dir, exp),
|
|
color="yellow",
|
|
err=True,
|
|
)
|
|
|
|
if confsrv.dirty:
|
|
if dry:
|
|
(_, dstfile) = tempfile.mkstemp()
|
|
else:
|
|
dstfile = bconfsrv
|
|
|
|
confsrv.store(conf=bconfsrv, dest=dstfile, insecure=True)
|
|
if dry:
|
|
before = []
|
|
after = []
|
|
try:
|
|
with open(bconfsrv) as fil:
|
|
before = fil.readlines()
|
|
except:
|
|
pass
|
|
try:
|
|
with open(dstfile) as fil:
|
|
after = fil.readlines()
|
|
os.unlink(dstfile)
|
|
except:
|
|
pass
|
|
diff = difflib.unified_diff(
|
|
before, after, fromfile=bconfsrv, tofile="{}.new".format(bconfsrv)
|
|
)
|
|
out = ""
|
|
for line in diff:
|
|
out += _color_diff(line)
|
|
if out:
|
|
click.echo_via_pager(out)
|
|
|
|
if confsrv.get("clientconfdir"):
|
|
bconfagent = os.path.join(confsrv.get("clientconfdir"), client)
|
|
else:
|
|
warn(
|
|
'Unable to find "clientconfdir" option, you will have to '
|
|
"setup the agent by your own"
|
|
)
|
|
bconfagent = os.devnull
|
|
|
|
if not os.path.exists(bconfagent):
|
|
agenttpl = """
|
|
password = abcdefgh
|
|
"""
|
|
|
|
if not dry:
|
|
with open(bconfagent, "w") as confagent:
|
|
confagent.write(agenttpl)
|
|
else:
|
|
before = []
|
|
after = ["{}\n".format(x) for x in agenttpl.splitlines()]
|
|
diff = difflib.unified_diff(
|
|
before, after, fromfile="None", tofile=bconfagent
|
|
)
|
|
out = ""
|
|
for line in diff:
|
|
out += _color_diff(line)
|
|
if out:
|
|
click.echo_via_pager(out)
|
|
|
|
else:
|
|
confagent = Config(bconfagent, parser, "cli")
|
|
confagent.set_default(bconfagent)
|
|
confagent.parse()
|
|
|
|
if confagent.get("password") != confcli.get("password"):
|
|
warn(
|
|
"It looks like the passwords in the {} and the {} files "
|
|
"mismatch. Burp-UI will not work properly until you fix "
|
|
"this".format(bconfcli, bconfagent)
|
|
)
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option(
|
|
"-c",
|
|
"--client",
|
|
default="bui",
|
|
help="Name of the burp client that will be used by Burp-UI " '(defaults to "bui")',
|
|
)
|
|
@click.option(
|
|
"-h",
|
|
"--host",
|
|
default="::1",
|
|
help='Address of the status server (defaults to "::1")',
|
|
)
|
|
@click.option("-t", "--tips", is_flag=True, help="Show you some tips")
|
|
def diag(client, host, tips):
|
|
"""Check Burp-UI is correctly setup."""
|
|
if app.config["BACKEND"] not in ["burp2", "parallel"]:
|
|
err("Sorry, you can only diag the 'burp2' and the 'parallel' backends")
|
|
sys.exit(1)
|
|
|
|
if not app.config["STANDALONE"]:
|
|
err("Sorry, only the standalone mode is supported")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
msg = app.load_modules()
|
|
except Exception as e:
|
|
msg = str(e)
|
|
|
|
if msg:
|
|
_die(msg, "diag")
|
|
|
|
from .app import get_redis_server
|
|
from .misc.backend.utils.constant import BURP_LISTEN_OPTION
|
|
from .misc.parser.utils import Config
|
|
|
|
def _value_in_option(value, option, section="Production"):
|
|
if section not in app.conf.options:
|
|
return False
|
|
if option not in app.conf.options[section]:
|
|
return False
|
|
return value in app.conf.options[section][option]
|
|
|
|
if (
|
|
"Production" in app.conf.options
|
|
and "redis" in app.conf.options["Production"]
|
|
and any(_value_in_option("redis", x) for x in ["storage", "session", "cache"])
|
|
):
|
|
try:
|
|
# detect missing modules
|
|
import socket
|
|
|
|
import celery # noqa
|
|
import redis as redis_client # noqa
|
|
|
|
rhost, rport, _ = get_redis_server(app)
|
|
ret = -1
|
|
for res in socket.getaddrinfo(
|
|
rhost, rport, socket.AF_UNSPEC, socket.SOCK_STREAM
|
|
):
|
|
if ret == 0:
|
|
break
|
|
af, socktype, proto, _, sa = res
|
|
try:
|
|
s = socket.socket(af, socktype, proto)
|
|
except socket.error:
|
|
continue
|
|
try:
|
|
ret = s.connect_ex(sa)
|
|
except:
|
|
continue
|
|
|
|
if ret != 0:
|
|
warn("Unable to contact the redis server, disabling it")
|
|
|
|
except ImportError:
|
|
warn(
|
|
"Unable to activate redis & celery. Did you ran the "
|
|
"'pip install \"burp-ui[celery]\"' and "
|
|
"'pip install \"burp-ui[gunicorn-extra]\"' commands first?"
|
|
)
|
|
|
|
if (
|
|
"Production" in app.conf.options
|
|
and "database" in app.conf.options["Production"]
|
|
and app.conf.options["Production"]["database"] != "none"
|
|
):
|
|
try:
|
|
from .ext.sql import db # noqa
|
|
except ImportError:
|
|
warn(
|
|
"It looks like some dependencies are missing. Did you ran "
|
|
"the 'pip install \"burp-ui[sql]\"' command first?"
|
|
)
|
|
|
|
section = "Burp"
|
|
if not app.conf.section_exists(section):
|
|
warn("Section [Burp] not found, looking for the old [Burp2] section instead.")
|
|
section = "Burp2"
|
|
if not app.conf.section_exists(section):
|
|
err("No [Burp*] section found at all!", err=False)
|
|
section = "Burp"
|
|
|
|
bconfcli = app.conf.options.get(section, {}).get("bconfcli") or getattr(
|
|
app.client, "burpconfcli"
|
|
)
|
|
bconfsrv = app.conf.options.get(section, {}).get("bconfsrv") or getattr(
|
|
app.client, "burpconfsrv"
|
|
)
|
|
|
|
try:
|
|
app.client.status()
|
|
except Exception as e:
|
|
if "Unable to spawn burp process" in str(e):
|
|
try:
|
|
app.client._spawn_burp(verbose=True)
|
|
except Exception as e:
|
|
msg = str(e)
|
|
else:
|
|
msg = str(e)
|
|
|
|
if msg:
|
|
err(msg, err=False)
|
|
if "could not connect" in msg:
|
|
warn(
|
|
"It looks like your burp-client can not reach your "
|
|
"burp-server. Please check both your 'server' setting in "
|
|
"your '{}' file and 'status_address' in your '{}' "
|
|
"file.".format(bconfcli, bconfsrv)
|
|
)
|
|
|
|
c_status_port = 4972
|
|
errors = False
|
|
if os.path.exists(bconfcli):
|
|
try:
|
|
parser = app.client.get_parser()
|
|
|
|
confcli = Config(bconfcli, parser, "srv")
|
|
confcli.set_default(bconfcli)
|
|
confcli.parse()
|
|
|
|
c_status_port = confcli.get("status_port", [4972])[0]
|
|
|
|
if confcli.get("cname") != client:
|
|
warn(
|
|
"The cname of your burp client does not match: "
|
|
"{} != {}".format(confcli.get("cname"), client)
|
|
)
|
|
errors = True
|
|
if confcli.get("server") != host:
|
|
warn(
|
|
"The burp server address does not match: "
|
|
"{} != {}".format(confcli.get("server"), host)
|
|
)
|
|
errors = True
|
|
except BUIserverException as exc:
|
|
err(str(exc))
|
|
errors = True
|
|
|
|
else:
|
|
err("No client conf file found: {} does not exist".format(bconfcli))
|
|
errors = True
|
|
|
|
if os.path.exists(bconfsrv):
|
|
try:
|
|
is_burp_2_2_10_plus = False
|
|
listen_opt = "status_address"
|
|
server_version = app.client.get_server_version()
|
|
if server_version and server_version >= BURP_LISTEN_OPTION:
|
|
is_burp_2_2_10_plus = True
|
|
listen_opt = "listen_status"
|
|
|
|
parser = app.client.get_parser()
|
|
|
|
confsrv = Config(bconfsrv, parser, "srv")
|
|
confsrv.set_default(bconfsrv)
|
|
confsrv.parse()
|
|
|
|
bind_index = -1
|
|
|
|
if host not in ["::1", "127.0.0.1"]:
|
|
bind = confsrv.get(listen_opt)
|
|
if is_burp_2_2_10_plus:
|
|
bind_list = list(bind)
|
|
for idx, line in enumerate(bind_list):
|
|
if line.endswith(":{}".format(c_status_port)):
|
|
bind = line.split(":")[0]
|
|
bind_index = idx
|
|
break
|
|
else:
|
|
warn(
|
|
"Unable to locate a 'listen_status' associated to your client "
|
|
"'status_port' ({})".format(c_status_port)
|
|
)
|
|
if (bind and bind not in [host, "::", "0.0.0.0"]) or not bind:
|
|
warn(
|
|
"It looks like your burp server is not exposing it's "
|
|
"status port in a way that is reachable by Burp-UI!"
|
|
)
|
|
info(
|
|
"You may want to set the '{0}' setting with "
|
|
"either '{1}{3}', '::{3}' or '0.0.0.0{3}' in the {2} file "
|
|
"in order to make Burp-UI work".format(
|
|
listen_opt,
|
|
host,
|
|
bconfsrv,
|
|
":{}".format(c_status_port) if is_burp_2_2_10_plus else "",
|
|
)
|
|
)
|
|
errors = True
|
|
|
|
status_port = confsrv.get("status_port", [4972])
|
|
if "max_status_children" not in confsrv:
|
|
do_warn = True
|
|
else:
|
|
max_status_children = confsrv.get("max_status_children", [])
|
|
do_warn = False
|
|
if not is_burp_2_2_10_plus:
|
|
for idx, value in enumerate(status_port):
|
|
if value == c_status_port:
|
|
bind_index = idx
|
|
break
|
|
if bind_index >= 0 and len(max_status_children) >= bind_index:
|
|
if max_status_children[bind_index] < 15:
|
|
do_warn = True
|
|
else:
|
|
if max_status_children[-1] < 15:
|
|
do_warn = True
|
|
if do_warn:
|
|
info(
|
|
"'max_status_children' is to low, you need to set it to "
|
|
"15 or more. Please edit your {} file.".format(bconfsrv)
|
|
)
|
|
errors = True
|
|
|
|
restore = []
|
|
if "restore_client" in confsrv:
|
|
restore = confsrv.getlist("restore_client")
|
|
|
|
if client not in restore:
|
|
warn(
|
|
"Your burp client is not listed as a 'restore_client'. "
|
|
"You won't be able to view other clients stats!"
|
|
)
|
|
errors = True
|
|
|
|
if "monitor_browse_cache" not in confsrv or not confsrv.get(
|
|
"monitor_browse_cache"
|
|
):
|
|
warn(
|
|
"For performance reasons, it is recommended to enable the "
|
|
"'monitor_browse_cache'."
|
|
)
|
|
errors = True
|
|
|
|
ca_client_dir = confsrv.get("ca_csr_dir")
|
|
if ca_client_dir and not os.path.exists(ca_client_dir):
|
|
try:
|
|
os.makedirs(ca_client_dir)
|
|
except IOError as exp:
|
|
_log(
|
|
'Unable to create "{}" dir: {}'.format(ca_client_dir, exp),
|
|
color="yellow",
|
|
err=True,
|
|
)
|
|
|
|
if confsrv.get("clientconfdir"):
|
|
bconfagent = os.path.join(confsrv.get("clientconfdir"), client)
|
|
else:
|
|
warn(
|
|
'Unable to find "clientconfdir" option. Something is wrong '
|
|
"with your setup."
|
|
)
|
|
bconfagent = "ihopethisfiledoesnotexistbecauseitisrelatedtoburpui"
|
|
|
|
if not os.path.exists(bconfagent) and bconfagent.startswith("/"):
|
|
warn("Unable to find the {} file.".format(bconfagent))
|
|
errors = True
|
|
else:
|
|
confagent = Config(bconfagent, parser, "cli")
|
|
confagent.set_default(bconfagent)
|
|
confagent.parse()
|
|
|
|
if confagent.get("password") != confcli.get("password"):
|
|
warn(
|
|
"It looks like the passwords in the {} and the {} files "
|
|
"mismatch. Burp-UI will not work properly until you fix "
|
|
"this.".format(bconfcli, bconfagent)
|
|
)
|
|
except BUIserverException as exc:
|
|
err(str(exc))
|
|
errors = True
|
|
else:
|
|
err(
|
|
"Unable to locate burp-server configuration: {} does not "
|
|
"exist.".format(bconfsrv)
|
|
)
|
|
errors = True
|
|
|
|
if errors:
|
|
if not tips:
|
|
err(
|
|
"Some errors have been found in your configuration. "
|
|
"Please make sure you ran this command with the right flags! "
|
|
"(see --help for details)."
|
|
)
|
|
else:
|
|
info(
|
|
"\n"
|
|
"Well, if you are sure about your settings, you can run the "
|
|
"following command to help you setup your Burp-UI agent. "
|
|
"(Note, the '--dry' flag is here to show you the "
|
|
"modifications that will be applied. Once you are OK with "
|
|
"those, you can re-run the command without the '--dry' flag):"
|
|
)
|
|
log(
|
|
' > bui-manage setup-burp --host="{}" --client="{}" --dry'.format(
|
|
host, client
|
|
)
|
|
)
|
|
else:
|
|
ok(
|
|
"Congratulations! It seems everything is alright. Burp-UI "
|
|
"should run without any issue now.",
|
|
)
|
|
|
|
|
|
@app.cli.command()
|
|
@click.option(
|
|
"-v",
|
|
"--verbose",
|
|
is_flag=True,
|
|
help="Dump parts of the config (Please double check no sensitive" " data leaked)",
|
|
)
|
|
@click.option(
|
|
"-l", "--load", is_flag=True, help="Load all configured modules for full summary"
|
|
)
|
|
def sysinfo(verbose, load):
|
|
"""Returns a couple of system informations to help debugging."""
|
|
import platform
|
|
|
|
from .desc import __release__, __version__
|
|
|
|
msg = None
|
|
if load:
|
|
try:
|
|
msg = app.load_modules()
|
|
except Exception as e:
|
|
msg = str(e)
|
|
|
|
backend = app.config["BACKEND"]
|
|
|
|
colors = {
|
|
"True": "green",
|
|
"False": "red",
|
|
}
|
|
embedded_ws = str(app.config["WITH_WS"])
|
|
available_ws = str(WS_AVAILABLE)
|
|
|
|
log(
|
|
"Python version: {}.{}.{}".format(
|
|
sys.version_info[0], sys.version_info[1], sys.version_info[2]
|
|
)
|
|
)
|
|
log("Burp-UI version: {} ({})".format(__version__, __release__))
|
|
log(
|
|
"OS: {}:{} ({})".format(
|
|
platform.system(), platform.release(), os.name
|
|
)
|
|
)
|
|
log("Single mode: {}".format(app.config["STANDALONE"]))
|
|
log("Backend: {}".format(backend))
|
|
log(
|
|
"WebSocket embedded: {}".format(
|
|
click.style(embedded_ws, fg=colors[embedded_ws])
|
|
)
|
|
)
|
|
log(
|
|
"WebSocket available: {}".format(
|
|
click.style(available_ws, colors[available_ws])
|
|
)
|
|
)
|
|
log("Config file: {}".format(app.config.conffile))
|
|
if load:
|
|
if not app.config["STANDALONE"] and not msg:
|
|
log("Agents:")
|
|
for agent, obj in app.client.servers.items():
|
|
client_version = server_version = "unknown"
|
|
try:
|
|
app.client.status(agent=agent)
|
|
client_version = app.client.get_client_version(agent=agent)
|
|
server_version = app.client.get_server_version(agent=agent)
|
|
except BUIserverException:
|
|
pass
|
|
alive = obj.ping()
|
|
if alive:
|
|
status = click.style("ALIVE", fg="green")
|
|
else:
|
|
status = click.style("DISCONNECTED", fg="red")
|
|
log(" - {} ({})".format(agent, status))
|
|
log(" * client version: {}".format(client_version))
|
|
log(" * server version: {}".format(server_version))
|
|
elif not msg:
|
|
server_version = "unknown"
|
|
try:
|
|
app.client.status()
|
|
server_version = app.client.get_server_version()
|
|
except BUIserverException:
|
|
pass
|
|
log("Burp client version: {}".format(app.client.client_version))
|
|
log("Burp server version: {}".format(server_version))
|
|
if verbose:
|
|
log(">>>>> Extra verbose informations:")
|
|
err("!!! PLEASE MAKE SURE NO SENSITIVE DATA GET EXPOSED !!!", err=False)
|
|
sections = [
|
|
"WebSocket",
|
|
"Burp",
|
|
"Production",
|
|
"Global",
|
|
]
|
|
sections.reverse()
|
|
for section in sections:
|
|
if section in app.config.options:
|
|
log()
|
|
log(" 8<{}BEGIN[{}]".format("-" * (67 - len(section)), section))
|
|
for key, val in app.config.options.get(section, {}).items():
|
|
log(" {} = {}".format(key, val))
|
|
log(" 8<{}END[{}]".format("-" * (69 - len(section)), section))
|
|
|
|
if load and msg:
|
|
_die(msg, "sysinfo")
|