Skip to content
Commits on Source (317)
......@@ -4,16 +4,21 @@
burpui-dev.cfg*
burpui/RELEASE
devel.sh
clean.sh
*.egg*
.tox
.reports
.coverage
.coveragerc
.ropeproject
.pylintrc
.pytest_cache
.python-version
.pre-commit-config.yaml
dist
_build
.tags
celerybeat-schedule
Pipfile*
pkgs/burp-ui-sql/burpui_sql/VERSION
pkgs/burp-ui-extra/burpui_extra/VERSION
pkgs/burp-ui-agent/burpui_agent
pkgs/burp-ui-monitor/burpui_monitor
variables:
GIT_DEPTH: 1
GIT_SUBMODULE_STRATEGY: recursive
BURP_VERSION: 2.4.0
PG_VERSION: 10
SRC_DIR: burpui
image: docker:git
stages:
- test
- build
- deploy
test:lint:
test:format:
stage: test
image: python:3.8
script:
- pip install black
- black --check .
tags:
- lint
except:
- tags
- rc
- demo
test:lint:3.7:
stage: test
image: python:2.7
image: python:3.7
script:
- pip install flake8 pylint
- make flake8
- pip install tox
- tox -e pep8
tags:
- lint
except:
- tags
- rc
- demo
test:py2.7:
test:lint:3.8:
stage: test
image: python:2.7
image: python:3.8
script:
- pip install tox
- tox -e py27
- tox -e pep8
tags:
- lint
except:
- tags
test:lint:3.9:
stage: test
image: python:3.9
script:
- pip install tox
- tox -e pep8
tags:
- lint
except:
- tags
- rc
- demo
test:lint:3.10:
stage: test
image: python:3.10
script:
- pip install tox
- tox -e pep8
tags:
- lint
except:
- tags
- rc
- demo
test:py:3.7:
stage: test
image: python:3.7
script:
- pip install tox
- mkdir .reports
- tox -e py37
tags:
- docker
except:
- tags
- rc
- demo
artifacts:
reports:
junit: .reports/burpui.junit.xml
test:py3.6:
test:py:3.8:
stage: test
image: python:3.6
image: python:3.8
script:
- pip install tox
- tox -e py36
- mkdir .reports
- tox -e py38
tags:
- docker
except:
- tags
artifacts:
reports:
junit: .reports/burpui.junit.xml
build:py2:
stage: build
image: python:2.7
test:py:3.9:
stage: test
image: python:3.9
script:
- /bin/bash tests/run_build.sh
- pip install tox
- mkdir .reports
- tox -e py39
tags:
- build
only:
- master@ziirish/burp-ui
- demo@ziirish/burp-ui
- docker
except:
- tags
- rc
- demo
artifacts:
paths:
- dist/
- meta/
expire_in: 2 mos
reports:
junit: .reports/burpui.junit.xml
test:py:3.10:
stage: test
image: python:3.10
script:
- pip install tox
- mkdir .reports
- tox -e py310
tags:
- docker
except:
- tags
- rc
- demo
artifacts:
reports:
junit: .reports/burpui.junit.xml
build:py3:
stage: build
image: python:3.6
image: python:3.8
script:
- /bin/bash tests/run_build.sh
- tests/build.sh
tags:
- build
only:
- master@ziirish/burp-ui
- demo@ziirish/burp-ui
- stable@ziirish/burp-ui
artifacts:
paths:
- dist/
......@@ -72,10 +160,10 @@ build:py3:
build:doc:
stage: build
image: python:3.6
image: python:3.8
script:
- pip install -U -r docs/requirements.txt
- make doc
- pip install -U .[rtd]
- cd docs && make html
tags:
- build
only:
......@@ -90,12 +178,12 @@ build:docker:latest:
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10 .)
- (cd docker/components/docker-burp && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54 .)
- docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION .)
- (cd docker/components/docker-burp && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION .)
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION
tags:
- registry
only:
......@@ -105,12 +193,12 @@ build:docker:release:
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_TAG -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10 .)
- (cd docker/components/docker-burp && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54 .)
- docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_TAG -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION .)
- (cd docker/components/docker-burp && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION .)
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_TAG
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION
only:
- tags
tags:
......@@ -120,12 +208,12 @@ build:docker:stable:
stage: build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:stable -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10 .)
- (cd docker/components/docker-burp && docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54 .)
- docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:latest --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:stable -f docker/Dockerfile .
- (cd docker/demo/docker-pg && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION .)
- (cd docker/components/docker-burp && docker build --cache-from $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION .)
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:stable
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:10
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:2.0.54
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/pgsql:$PG_VERSION
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME/burp:$BURP_VERSION
only:
- stable@ziirish/burp-ui
tags:
......@@ -137,6 +225,7 @@ build:docker:demo:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build --pull -t $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:demo -f docker/Dockerfile .
- docker push $CI_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:demo
- apk add --no-cache curl
- "curl $SENTRY_WEBHOOK -X POST -H 'Content-Type: application/json' -d '{\"version\": \"'$CI_COMMIT_REF_NAME'_'$CI_COMMIT_SHA'\"}'"
only:
- demo@ziirish/burp-ui
......@@ -148,12 +237,11 @@ deploy:demo:
script:
- find docker/demo/ -name "install" -o -name "init" | xargs sed -i "s/@build@/$CI_COMMIT_SHA/"
- cd docker/demo/ && find . -maxdepth 1 -type d -a ! -name dist -exec cp -r ../../dist "{}/" \; -exec cp -r ../../meta "{}/" \; && cd ../..
- find docker/demo/ -name "Dockerfile" | xargs sed -i "s,^.*@ARTIFACTS@.*$,COPY dist/*.tar.gz /tmp/burpui.dev.tar.gz,;s,^.*@BUIAGENT_ARTIFACTS@.*$,COPY meta/burp-ui-agent*.tar.gz /tmp/burp-ui-agent.dev.tar.gz,"
- find docker/demo/ -name "Dockerfile" | xargs sed -i "s,^.*@ARTIFACTS@.*$,COPY dist/*.tar.gz /tmp/burpui.dev.tar.gz,;s,^.*@BUIAGENT_ARTIFACTS@.*$,COPY meta/burp-ui-agent*.tar.gz /tmp/burp-ui-agent.dev.tar.gz,;s,^.*@BUIMONITOR_ARTIFACTS@.*$,COPY meta/burp-ui-monitor*.tar.gz /tmp/burp-ui-monitor.dev.tar.gz,"
- test -d /srv/demo/docker && rm -rf /srv/demo/docker
- cp -r docker/demo/ /srv/demo/docker
- cd /srv/demo/docker/
# old docker client, we need the "-e" flag
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN -e $DUMMY_EMAIL $CI_REGISTRY
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker-compose build --pull
- docker-compose stop
- docker-compose rm -f
......@@ -165,3 +253,5 @@ deploy:demo:
environment:
name: demo
url: https://demo.burp-ui.org/
variables:
DOCKER_HOST: unix:///var/run/docker.sock
......@@ -47,8 +47,8 @@ burp-2.1.18
```
$ bui-manage sysinfo
Python version: 3.6.1
Burp-UI version: 0.5.0 (stable)
Python version: 3.6.5
Burp-UI version: 0.6.1 (stable)
Single mode: True
Backend version: 2
WebSocket embedded: False
......
# https://docs.readthedocs.io/en/latest/yaml-config.html
version: 2
build:
image: latest
python:
version: 3.7
install:
- method: pip
path: .
extra_requirements:
- rtd
Changelog
=========
1.1.1 (07/07/2023)
------------------
- fix: remove duplicate compare_type from alembic config
- fix: make sure we don't initialize the DB connector twice
1.1.0 (05/01/2023)
------------------
- fix: update dependencies and fix unit tests `#352 <https://git.ziirish.me/ziirish/burp-ui/issues/352>`_
- fix: platform.dist is no longer part of the std lib
- fix: adapt send_file usage
1.0.0 (11/06/2022)
------------------
- **BREAKING**: the *single* and *version* options within the ``[Global]`` section have been removed in favor of a new unified *backend* option
- **BREAKING**: a change introduced by `#284 <https://git.ziirish.me/ziirish/burp-ui/issues/284>`_ may return wrong timestamps for backups made with burp-server <= 2.1.10 if your current burp-server is >= 2.1.10
- **BREAKING**: the authentication backends section have been renamed with the ``:AUTH`` suffix
- **BREAKING**: the ``prefix`` option has been moved from the ``[Global]`` configuration section to the ``[Production]`` one
- Add: new `audit logging <https://git.ziirish.me/ziirish/burp-ui/issues/260>`_ system
- Add: new ``bui-monitor`` processes pool + ``async`` backend to parallelize some requests `#278 <https://git.ziirish.me/ziirish/burp-ui/issues/278>`_
- Add: new `listen` and `listen_status` options in burp-2.2.10 `#279 <https://git.ziirish.me/ziirish/burp-ui/issues/279>`_
- Add: new `order` keyword in ACL definitions in order to decide whether `rw` should be evaluated first or not `#305 <https://git.ziirish.me/ziirish/burp-ui/issues/305>`__
- Add: new `exclude` keyword in ACL definitions in order to exclude some clients from the rules `#305 <https://git.ziirish.me/ziirish/burp-ui/issues/305>`__
- Add: new *static templates* that allow you to create *onetime* (variables) templates `#280 <https://git.ziirish.me/ziirish/burp-ui/issues/280>`_
- Add: return last backup attempt `#309 <https://git.ziirish.me/ziirish/burp-ui/issues/309>`_
- Add: allow to hide selected clients/servers `#282 <https://git.ziirish.me/ziirish/burp-ui/issues/282>`_
- Add: allow to delete clients data upon removal `#232 <https://git.ziirish.me/ziirish/burp-ui/issues/232>`_
- Add: allow to create clients from templates in one call `#266 <https://git.ziirish.me/ziirish/burp-ui/issues/266>`_
- Add: allow to rename clients/templates `#274 <https://git.ziirish.me/ziirish/burp-ui/issues/274>`_
- Add: allow to set a custom timezone in which to display UI dates `#329 <https://git.ziirish.me/ziirish/burp-ui/issues/329>`_
- Fix: sync pkgs requirements with burp-ui's `#300 <https://git.ziirish.me/ziirish/burp-ui/issues/300>`__
- Fix: wrong command suggestion `#296 <https://git.ziirish.me/ziirish/burp-ui/issues/296>`__
- Fix: allow templates removal `#290 <https://git.ziirish.me/ziirish/burp-ui/issues/290>`__
- Fix: don't preload LDAP users `#270 <https://git.ziirish.me/ziirish/burp-ui/issues/270>`__
- Fix: don't screw up configuration files `#333 <https://git.ziirish.me/ziirish/burp-ui/issues/333>`__
- Fix: issue `#268 <https://git.ziirish.me/ziirish/burp-ui/issues/268>`_
- `Full changelog <https://git.ziirish.me/ziirish/burp-ui/compare/0.6.0...master>`__
0.6.6 (04/02/2019)
------------------
- Fix: python 3.7 compatibility `#304 <https://git.ziirish.me/ziirish/burp-ui/issues/304>`__
- Fix: agent cannot start `#302 <https://git.ziirish.me/ziirish/burp-ui/issues/302>`__
0.6.5 (03/27/2019)
------------------
- Fix: packaging issue
0.6.4 (03/26/2019)
------------------
- Fix: sync pkgs requirements with burp-ui's `#300 <https://git.ziirish.me/ziirish/burp-ui/issues/300>`__
0.6.3 (03/13/2019)
------------------
- Fix: don't preload LDAP users `#270 <https://git.ziirish.me/ziirish/burp-ui/issues/270>`__
0.6.2 (03/05/2019)
------------------
- Fix: wrong command suggestion `#296 <https://git.ziirish.me/ziirish/burp-ui/issues/296>`__
- Fix: allow templates removal `#290 <https://git.ziirish.me/ziirish/burp-ui/issues/290>`__
- Fix: support burp-2.2.16 `#291 <https://git.ziirish.me/ziirish/burp-ui/issues/291>`_
- Fix: issue `#268 <https://git.ziirish.me/ziirish/burp-ui/issues/268>`_
0.6.1 (05/17/2018)
------------------
- Improvement: Don't cache any data when there is a running backup
- Fix: cannot display bui-agent version
- Fix: live-monitor was broken do to a missing cache
- `Full changelog <https://git.ziirish.me/ziirish/burp-ui/compare/0.6.0...0.6.1>`__
0.6.0 (05/14/2018)
------------------
......
......@@ -12,5 +12,6 @@ Graham Keeling (main author of Burp)
larsen0815
Johannes Lerch
slarti5191
Lukas Schreiner
Robert Tichy
Benjamin `ziirish` SANS (main author)
......@@ -7,6 +7,7 @@ include burpui/VERSION
include burpui/RELEASE
include requirements.txt
include share/burpui/etc/burpui.sample.cfg
include share/burpui/etc/buimonitor.sample.cfg
include babel.cfg
graft contrib
graft burpui
......
.PHONY: all test clean_coverage doc doc_coverage clean pep8 pyflakes check
all:
@echo 'test run the unit tests'
@echo 'flake8 check pep8 compliance'
@echo 'check make sure you are ready to commit'
@echo 'clean cleanup the source tree'
doc_coverage:
@echo 'Running docstring coverage...'
@docstring-coverage burpui
test: clean_coverage
@echo 'Running all tests...'
@nosetests --with-coverage --cover-package=burpui test/test_burpui.py
doc:
@echo 'Generating documentation...'
@cd docs && make html
clean:
@find . -type d -name "__pycache__" -exec rm -rf "{}" \; || true
@find . -type f -name "*.pyc" -delete || true
@rm -rf build dist burp_ui.egg-info docs/_build || true
@cd docs && make clean
clean_coverage:
@rm -f .coverage
flake8:
@echo 'Checking pep8 compliance and errors...'
@flake8 --ignore=E501,E722 burpui
check: pep8 pyflakes doc_coverage test
Burp-UI
=======
.. image:: https://git.ziirish.me/ziirish/burp-ui/badges/stable/build.svg
.. image:: https://git.ziirish.me/ziirish/burp-ui/badges/master/pipeline.svg
:target: https://git.ziirish.me/ziirish/burp-ui/pipelines
:alt: Build Status
.. image:: https://git.ziirish.me/ziirish/burp-ui/badges/stable/coverage.svg
.. image:: https://git.ziirish.me/ziirish/burp-ui/badges/master/coverage.svg
:target: https://git.ziirish.me/ziirish/burp-ui/pipelines
:alt: Test coverage
.. image:: https://readthedocs.org/projects/burp-ui/badge/?version=stable
:target: https://readthedocs.org/projects/burp-ui/?badge=stable
.. image:: https://readthedocs.org/projects/burp-ui/badge/?version=latest
:target: https://readthedocs.org/projects/burp-ui/?badge=latest
:alt: Documentation Status
.. contents::
......@@ -21,8 +21,8 @@ Introduction
Screenshots
^^^^^^^^^^^
.. image:: https://git.ziirish.me/ziirish/burp-ui/raw/stable/docs/_static/burp-ui.gif
:target: https://git.ziirish.me/ziirish/burp-ui/blob/stable/docs/_static/burp-ui.gif
.. image:: https://git.ziirish.me/ziirish/burp-ui/raw/master/docs/_static/burp-ui.gif
:target: https://git.ziirish.me/ziirish/burp-ui/blob/master/docs/_static/burp-ui.gif
Demo
^^^^
......@@ -84,6 +84,8 @@ I have closed the *github tracker* to have a unique tracker system.
Also please, read the `Contributing`_ page before reporting any issue to make
sure we have all the informations to help you.
Bug report that don't comply with the rules will likely be **ignored** because
my spare time is quite limited.
See also
--------
......@@ -143,7 +145,7 @@ would not exist without `Burp`_.
.. _Flask: http://flask.pocoo.org/
.. _License: https://git.ziirish.me/ziirish/burp-ui/blob/master/LICENSE
.. _Burp: http://burp.grke.org/
.. _burpui.cfg: https://git.ziirish.me/ziirish/burp-ui/blob/stable/share/burpui/etc/burpui.sample.cfg
.. _burp-ui.readthedocs.io: https://burp-ui.readthedocs.io/en/stable/
.. _FAQ: https://burp-ui.readthedocs.io/en/stable/faq.html
.. _Contributing: https://burp-ui.readthedocs.io/en/stable/contributing.html
.. _burpui.cfg: https://git.ziirish.me/ziirish/burp-ui/blob/master/share/burpui/etc/burpui.sample.cfg
.. _burp-ui.readthedocs.io: https://burp-ui.readthedocs.io/en/latest/
.. _FAQ: https://burp-ui.readthedocs.io/en/latest/faq.html
.. _Contributing: https://burp-ui.readthedocs.io/en/latest/contributing.html
......@@ -9,17 +9,12 @@ jQuery/Bootstrap
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
import sys
import warnings
from .app import init
from .app import create_app
warnings.simplefilter('always', RuntimeWarning)
if sys.version_info < (3, 0): # pragma: no cover
reload(sys)
sys.setdefaultencoding('utf-8')
warnings.simplefilter("always", RuntimeWarning)
# backward compatibility
create_app = init
init = create_app
......@@ -12,39 +12,77 @@ jQuery/Bootstrap
"""
import os
import sys
from argparse import ArgumentParser, REMAINDER
from argparse import REMAINDER, ArgumentParser
ROOT = os.path.dirname(os.path.realpath(__file__))
# Try to load modules from our current env first
sys.path.insert(0, os.path.join(ROOT, '..'))
sys.path.insert(0, os.path.join(ROOT, ".."))
def parse_args(mode=True, name=None):
mname = name
if not name:
mname = 'burp-ui'
mname = "burp-ui"
parser = ArgumentParser(prog=mname)
parser.add_argument('-v', '--verbose', dest='log', help='increase output verbosity (e.g., -vv is more verbose than -v)', action='count')
parser.add_argument('-d', '--debug', dest='debug', help='enable debug mode', action='store_true')
parser.add_argument('-V', '--version', dest='version', help='print version and exit', action='store_true')
parser.add_argument('-c', '--config', dest='config', help='burp-ui configuration file', metavar='<CONFIG>')
parser.add_argument('-l', '--logfile', dest='logfile', help='output logs in defined file', metavar='<FILE>')
parser.add_argument('-i', '--migrations', dest='migrations', help='migrations directory', metavar='<MIGRATIONSDIR>')
parser.add_argument('remaining', nargs=REMAINDER)
parser.add_argument(
"-v",
"--verbose",
dest="log",
help="increase output verbosity (e.g., -vv is more verbose than -v)",
action="count",
)
parser.add_argument(
"-d", "--debug", dest="debug", help="enable debug mode", action="store_true"
)
parser.add_argument(
"-V",
"--version",
dest="version",
help="print version and exit",
action="store_true",
)
parser.add_argument(
"-c",
"--config",
dest="config",
help="burp-ui configuration file",
metavar="<CONFIG>",
)
parser.add_argument(
"-l",
"--logfile",
dest="logfile",
help="output logs in defined file",
metavar="<FILE>",
)
parser.add_argument(
"-i",
"--migrations",
dest="migrations",
help="migrations directory",
metavar="<MIGRATIONSDIR>",
)
parser.add_argument("remaining", nargs=REMAINDER)
if mode:
parser.add_argument('-m', '--mode', dest='mode', help='application mode', metavar='<agent|server|celery|manage|legacy>')
parser.add_argument(
"-m",
"--mode",
dest="mode",
help="application mode",
metavar="<agent|server|celery|manage|monitor|legacy>",
)
options, unknown = parser.parse_known_args()
if mode and options.mode and options.mode not in ['celery', 'manage', 'server']:
if mode and options.mode and options.mode not in ["celery", "manage", "server"]:
options = parser.parse_args()
unknown = []
if options.version:
from burpui.desc import __title__, __version__, __release__
ver = '{}: v{}'.format(mname or __title__, __version__)
from burpui.desc import __release__, __title__, __version__
ver = "{}: v{}".format(mname or __title__, __version__)
if options.log:
ver = '{} ({})'.format(ver, __release__)
ver = "{} ({})".format(ver, __release__)
print(ver)
sys.exit(0)
......@@ -57,18 +95,20 @@ def main():
"""
options, unknown = parse_args(mode=True)
if not options.mode or options.mode == 'server':
if not options.mode or options.mode == "server":
server(options, unknown)
elif options.mode == 'agent':
elif options.mode == "agent":
agent(options)
elif options.mode == 'celery':
elif options.mode == "celery":
celery()
elif options.mode == 'manage':
elif options.mode == "manage":
manage()
elif options.mode == 'legacy':
elif options.mode == "monitor":
monitor(options)
elif options.mode == "legacy":
legacy(options, unknown)
else:
print('Wrong mode!')
print("Wrong mode!")
sys.exit(1)
......@@ -84,66 +124,94 @@ def server(options=None, unknown=None):
if options.config:
conf = lookup_file(options.config, guess=False)
else:
if 'BUI_CONFIG' in env:
conf = env['BUI_CONFIG']
if "BUI_CONFIG" in env:
conf = env["BUI_CONFIG"]
else:
conf = lookup_file()
check_config(conf)
if os.path.isdir('burpui'):
env['FLASK_APP'] = 'burpui/cli.py'
if os.path.isdir("burpui"):
env["FLASK_APP"] = "burpui/cli.py"
else:
env['FLASK_APP'] = 'burpui.cli'
env['BUI_CONFIG'] = conf
env['BUI_VERBOSE'] = str(options.log)
env["FLASK_APP"] = "burpui.cli"
env["BUI_CONFIG"] = conf
env["BUI_VERBOSE"] = str(options.log)
if options.logfile:
env['BUI_LOGFILE'] = options.logfile
env["BUI_LOGFILE"] = options.logfile
if options.debug:
env['BUI_DEBUG'] = '1'
env['FLASK_DEBUG'] = '1'
env['BUI_MODE'] = 'server'
args = [
'flask',
'run'
]
env["BUI_DEBUG"] = "1"
env["FLASK_DEBUG"] = "1"
env["BUI_MODE"] = "server"
args = ["flask", "run"]
args += unknown
args += [x for x in options.remaining if x != '--']
args += [x for x in options.remaining if x != "--"]
os.execvpe(args[0], args, env)
def agent(options=None):
from gevent import monkey
from burpui.agent import BUIAgent as Agent
import trio
from burpui.engines.agent import BUIAgent as Agent
from burpui.utils import lookup_file
from burpui._compat import patch_json
monkey.patch_all()
patch_json()
if not options:
options, _ = parse_args(mode=False, name="bui-agent")
conf = ["buiagent.cfg", "buiagent.sample.cfg"]
if options.config:
conf = lookup_file(options.config, guess=False)
else:
conf = lookup_file(conf)
check_config(conf)
agent = Agent(conf, options.log, options.logfile)
trio.run(agent.run)
def monitor(options=None):
import trio
from burpui.engines.monitor import MonitorPool
from burpui.utils import lookup_file
if not options:
options, _ = parse_args(mode=False, name='bui-agent')
options, _ = parse_args(mode=False, name="bui-agent")
conf = ['buiagent.cfg', 'buiagent.sample.cfg']
conf = ["buimonitor.cfg", "buimonitor.sample.cfg"]
if options.config:
conf = lookup_file(options.config, guess=False)
else:
conf = lookup_file(conf)
check_config(conf)
agent = Agent(conf, options.log, options.logfile, options.debug)
agent.run()
monitor = MonitorPool(conf, options.log, options.logfile)
trio.run(monitor.run)
def celery():
from burpui.utils import lookup_file
parser = ArgumentParser('bui-celery')
parser.add_argument('-c', '--config', dest='config', help='burp-ui configuration file', metavar='<CONFIG>')
parser.add_argument('-t', '--type', dest='type', help='celery mode', metavar='<worker|beat|flower>')
parser.add_argument('-m', '--mode', dest='mode', help='application mode', metavar='<agent|server|worker|manage|legacy>')
parser.add_argument('remaining', nargs=REMAINDER)
parser = ArgumentParser("bui-celery")
parser.add_argument(
"-c",
"--config",
dest="config",
help="burp-ui configuration file",
metavar="<CONFIG>",
)
parser.add_argument(
"-t", "--type", dest="type", help="celery mode", metavar="<worker|beat|flower>"
)
parser.add_argument(
"-m",
"--mode",
dest="mode",
help="application mode",
metavar="<agent|server|worker|manage|legacy>",
)
parser.add_argument("remaining", nargs=REMAINDER)
options, unknown = parser.parse_known_args()
env = os.environ
......@@ -151,18 +219,18 @@ def celery():
if options.config:
conf = lookup_file(options.config, guess=False)
else:
if 'BUI_CONFIG' in env:
conf = env['BUI_CONFIG']
if "BUI_CONFIG" in env:
conf = env["BUI_CONFIG"]
else:
conf = lookup_file()
if options.type:
celery_mode = options.type
else:
celery_mode = 'worker'
celery_mode = "worker"
# make conf path absolute
if not conf.startswith('/'):
if not conf.startswith("/"):
curr = os.getcwd()
conf = os.path.join(curr, conf)
......@@ -170,17 +238,12 @@ def celery():
os.chdir(ROOT)
env['BUI_MODE'] = 'celery'
env['BUI_CONFIG'] = conf
env["BUI_MODE"] = "celery"
env["BUI_CONFIG"] = conf
args = [
'celery',
celery_mode,
'-A',
'worker.celery'
]
args = ["celery", "-A", "engines.worker.celery", celery_mode]
args += unknown
args += [x for x in options.remaining if x != '--']
args += [x for x in options.remaining if x != "--"]
os.execvpe(args[0], args, env)
......@@ -188,48 +251,78 @@ def celery():
def manage():
from burpui.utils import lookup_file
parser = ArgumentParser('bui-manage')
parser.add_argument('-v', '--verbose', dest='log', help='increase output verbosity (e.g., -vv is more verbose than -v)', action='count')
parser.add_argument('-c', '--config', dest='config', help='burp-ui configuration file', metavar='<CONFIG>')
parser.add_argument('-i', '--migrations', dest='migrations', help='migrations directory', metavar='<MIGRATIONSDIR>')
parser.add_argument('-m', '--mode', dest='mode', help='application mode', metavar='<agent|server|worker|manage|legacy>')
parser.add_argument('-l', '--logfile', dest='logfile', help='output logs in defined file', metavar='<FILE>')
parser.add_argument('remaining', nargs=REMAINDER)
parser = ArgumentParser("bui-manage")
parser.add_argument(
"-v",
"--verbose",
dest="log",
help="increase output verbosity (e.g., -vv is more verbose than -v)",
action="count",
)
parser.add_argument(
"-c",
"--config",
dest="config",
help="burp-ui configuration file",
metavar="<CONFIG>",
)
parser.add_argument(
"-i",
"--migrations",
dest="migrations",
help="migrations directory",
metavar="<MIGRATIONSDIR>",
)
parser.add_argument(
"-m",
"--mode",
dest="mode",
help="application mode",
metavar="<agent|server|worker|manage|legacy>",
)
parser.add_argument(
"-l",
"--logfile",
dest="logfile",
help="output logs in defined file",
metavar="<FILE>",
)
parser.add_argument("remaining", nargs=REMAINDER)
options, unknown = parser.parse_known_args()
env = os.environ
if options.logfile:
env['BUI_LOGFILE'] = options.logfile
env["BUI_LOGFILE"] = options.logfile
if options.config:
conf = lookup_file(options.config, guess=False)
else:
if 'BUI_CONFIG' in env:
conf = env['BUI_CONFIG']
if "BUI_CONFIG" in env:
conf = env["BUI_CONFIG"]
else:
conf = lookup_file()
check_config(conf)
if options.migrations:
migrations = lookup_file(options.migrations, guess=False, directory=True, check=False)
migrations = lookup_file(
options.migrations, guess=False, directory=True, check=False
)
else:
migrations = lookup_file('migrations', directory=True)
migrations = lookup_file("migrations", directory=True)
env['BUI_MODE'] = 'manage'
env['BUI_CONFIG'] = conf
env['BUI_VERBOSE'] = str(options.log)
env["BUI_MODE"] = "manage"
env["BUI_CONFIG"] = conf
env["BUI_VERBOSE"] = str(options.log)
if migrations:
env['BUI_MIGRATIONS'] = migrations
if os.path.isdir('burpui') and os.path.isfile('burpui/cli.py'):
env['FLASK_APP'] = 'burpui/cli.py'
env["BUI_MIGRATIONS"] = migrations
if os.path.isdir("burpui") and os.path.isfile("burpui/cli.py"):
env["FLASK_APP"] = "burpui/cli.py"
else:
env['FLASK_APP'] = 'burpui.cli'
env["FLASK_APP"] = "burpui.cli"
args = [
'flask'
]
args = ["flask"]
args += unknown
args += [x for x in options.remaining if x != '--']
args += [x for x in options.remaining if x != "--"]
os.execvpe(args[0], args, env)
......@@ -240,47 +333,44 @@ def legacy(options=None, unknown=None):
if unknown is None:
unknown = []
if not options:
options, unknown = parse_args(mode=False, name='burpui-legacy')
options, unknown = parse_args(mode=False, name="burpui-legacy")
env = os.environ
if options.config:
conf = lookup_file(options.config, guess=False)
else:
if 'BUI_CONFIG' in env:
conf = env['BUI_CONFIG']
if "BUI_CONFIG" in env:
conf = env["BUI_CONFIG"]
else:
conf = lookup_file()
check_config(conf)
env['BUI_MODE'] = 'legacy'
env['BUI_CONFIG'] = conf
if os.path.isdir('burpui'):
env['FLASK_APP'] = 'burpui/cli.py'
env["BUI_MODE"] = "legacy"
env["BUI_CONFIG"] = conf
if os.path.isdir("burpui"):
env["FLASK_APP"] = "burpui/cli.py"
else:
env['FLASK_APP'] = 'burpui.cli'
env['BUI_VERBOSE'] = str(options.log)
env["FLASK_APP"] = "burpui.cli"
env["BUI_VERBOSE"] = str(options.log)
if options.logfile:
env['BUI_LOGFILE'] = options.logfile
env["BUI_LOGFILE"] = options.logfile
if options.debug:
env['BUI_DEBUG'] = '1'
env['FLASK_DEBUG'] = '1'
env["BUI_DEBUG"] = "1"
env["FLASK_DEBUG"] = "1"
args = [
'flask',
'legacy'
]
args = ["flask", "legacy"]
args += unknown
args += [x for x in options.remaining if x != '--']
args += [x for x in options.remaining if x != "--"]
os.execvpe(args[0], args, env)
def check_config(conf):
if not conf:
raise IOError('No configuration file found')
raise IOError("No configuration file found")
if not os.path.isfile(conf):
raise IOError('File does not exist: \'{0}\''.format(conf))
raise IOError("File does not exist: '{0}'".format(conf))
if __name__ == '__main__':
if __name__ == "__main__":
main()
......@@ -7,72 +7,25 @@
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
import re
import sys
import pickle # noqa
from urllib.parse import quote, unquote, urljoin, urlparse # noqa
try:
import cPickle as pickle # noqa
except ImportError:
import pickle # noqa
if sys.version_info[0] >= 3:
PY3 = True
from urllib.parse import unquote, quote, urlparse, urljoin # noqa
text_type = str
string_types = (str,)
def iterkeys(d, *args, **kwargs):
return iter(d.keys(*args, **kwargs))
def itervalues(d, *args, **kwargs):
return iter(d.values(*args, **kwargs))
def iteritems(d, *args, **kwargs):
return iter(d.items(*args, **kwargs))
def iterlists(d, *args, **kwargs):
return iter(d.lists(*args, **kwargs))
def iterlistvalues(d, *args, **kwargs):
return iter(d.listvalues(*args, **kwargs))
else:
PY3 = False
from urllib import unquote, quote # noqa
from urlparse import urlparse, urljoin # noqa
text_type = unicode
string_types = (str, unicode)
def iterkeys(d, *args, **kwargs):
return d.iterkeys(*args, **kwargs)
def itervalues(d, *args, **kwargs):
return d.itervalues(*args, **kwargs)
def iteritems(d, *args, **kwargs):
return d.iteritems(*args, **kwargs)
def iterlists(d, *args, **kwargs):
return d.iterlists(*args, **kwargs)
def iterlistvalues(d, *args, **kwargs):
return d.iterlistvalues(*args, **kwargs)
text_type = str
string_types = (str,)
def to_bytes(text):
"""Transform string to bytes."""
if isinstance(text, text_type):
text = text.encode('utf-8')
return text or b''
text = text.encode("utf-8")
return text or b""
def to_unicode(input_bytes, encoding='utf-8'):
def to_unicode(input_bytes, encoding="utf-8"):
"""Decodes input_bytes to text if needed."""
if not isinstance(input_bytes, string_types):
input_bytes = input_bytes.decode(encoding)
elif re.match(r'\\u[0-9a-f]{4}', input_bytes):
input_bytes = input_bytes.decode('unicode-escape')
return input_bytes or u''
return input_bytes or ""
# maps module name -> attribute name -> original item
......@@ -86,24 +39,24 @@ def patch_item(module, attr, newitem, newmodule=None):
olditem = getattr(module, attr, NONE)
if olditem is not NONE:
saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
if newmodule and not getattr(newmodule, 'ori_' + attr, None):
setattr(newmodule, 'ori_' + attr, olditem)
if not getattr(newmodule, 'ori_' + attr, None):
if newmodule and not getattr(newmodule, "ori_" + attr, None):
setattr(newmodule, "ori_" + attr, olditem)
if not getattr(newmodule, "ori_" + attr, None):
setattr(module, attr, newitem)
def patch_module(name, items=None):
toimport = items or []
mod = __name__
if '.' in mod:
mod = mod.split('.')[0]
replace_module = __import__('{}._{}'.format(mod, name), fromlist=toimport)
if "." in mod:
mod = mod.split(".")[0]
replace_module = __import__("{}._{}".format(mod, name), fromlist=toimport)
module_name = name
module = __import__(module_name)
if items is None:
items = getattr(replace_module, '__implements__', None)
items = getattr(replace_module, "__implements__", None)
if items is None:
raise AttributeError('%r does not have __implements__' % replace_module)
raise AttributeError("%r does not have __implements__" % replace_module)
for attr in items:
patch_item(module, attr, getattr(replace_module, attr), replace_module)
......@@ -114,4 +67,4 @@ def patch_json():
except ImportError:
# ujson is not available, we won't patch anything
return
patch_module('json', ['dumps', 'loads'])
patch_module("json", ["dumps", "loads"])
......@@ -8,20 +8,19 @@
"""
import ujson
from six import viewkeys
__implements__ = ['dumps', 'loads']
__implements__ = ["dumps", "loads"]
ori_dumps = None
ori_loads = None
IMPLEMENTED_DUMPS_KWARGS = [
'ensure_ascii',
'double_precision',
'encode_html_chars',
'sort_keys',
"ensure_ascii",
"double_precision",
"encode_html_chars",
"sort_keys",
]
IMPLEMENTED_LOADS_KWARGS = [
'precise_float',
"precise_float",
]
......@@ -30,7 +29,7 @@ IMPLEMENTED_LOADS_KWARGS = [
def dumps(*args, **kwargs):
keys = []
if kwargs:
keys = viewkeys(kwargs)
keys = kwargs.keys()
for key in keys:
if key not in IMPLEMENTED_DUMPS_KWARGS:
return ori_dumps(*args, **kwargs)
......@@ -43,7 +42,7 @@ def dumps(*args, **kwargs):
def loads(*args, **kwargs):
keys = []
if kwargs:
keys = viewkeys(kwargs)
keys = kwargs.keys()
for key in keys:
if key not in IMPLEMENTED_LOADS_KWARGS:
return ori_loads(*args, **kwargs)
......
......@@ -14,4 +14,4 @@ from . import create_app
# This is a lie we are not really unittesting, but we want to avoid the v2
# errors
app = create_app(conf='/dev/null', gunicorn=False, unittest=True)
app = create_app(conf="/dev/null", gunicorn=False, unittest=True)
# -*- coding: utf8 -*-
"""
.. module:: burpui.agent
:platform: Unix
:synopsis: Burp-UI agent module.
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
import os
import struct
import re
import time
import sys
import json
import logging
import traceback
from gevent.lock import RLock
from gevent.pool import Pool
from gevent.server import StreamServer
from logging.handlers import RotatingFileHandler
from .exceptions import BUIserverException
from .misc.backend.interface import BUIbackend
from ._compat import pickle, to_bytes, to_unicode
from .utils import BUIlogging
from .config import config
from .desc import __version__
try:
from sendfile import sendfile
USE_SENDFILE = True
except ImportError:
USE_SENDFILE = False
G_PORT = 10000
G_BIND = u'::'
G_SSL = False
G_VERSION = 2
G_SSLCERT = u''
G_SSLKEY = u''
G_PASSWORD = u'password'
DISCLOSURE = 5
lock = RLock()
class BurpHandler(BUIbackend):
# These functions MUST be implemented because we inherit an abstract class.
# The hack here is to get the list of the functions and let the interpreter
# think we don't have to implement them.
# Thanks to this list, we know what function are implemented by our backend.
foreign = BUIbackend.__abstractmethods__
BUIbackend.__abstractmethods__ = frozenset()
def __init__(self, vers=2, logger=None, conf=None):
self.vers = vers
self.logger = logger
top = __name__
if '.' in top:
top = top.split('.')[0]
module = '{0}.misc.backend.burp{1}'.format(top, self.vers)
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
mod = __import__(module, fromlist=['Burp'])
Client = mod.Burp
self.backend = Client(conf=conf)
except Exception as e:
self.logger.error('{}\n\nFailed loading backend for Burp version {}: {}'.format(traceback.format_exc(), self.vers, str(e)))
sys.exit(2)
def __getattribute__(self, name):
# always return this value because we need it and if we don't do that
# we'll end up with an infinite loop
if name == 'foreign' or name == 'backend':
return object.__getattribute__(self, name)
# now we can retrieve the 'foreign' list and know if the object called
# is in the backend
if name in self.foreign:
return getattr(self.backend, name)
try:
return getattr(self.backend, name)
except AttributeError:
pass
return object.__getattribute__(self, name)
class BUIAgent(BUIbackend, BUIlogging):
BUIbackend.__abstractmethods__ = frozenset()
defaults = {
'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.debug = debug
self.padding = 1
level = level or 0
if level > logging.NOTSET:
logging.addLevelName(DISCLOSURE, 'DISCLOSURE')
levels = [
logging.CRITICAL,
logging.ERROR,
logging.WARNING,
logging.INFO,
logging.DEBUG,
DISCLOSURE
]
if level >= len(levels):
level = len(levels) - 1
lvl = levels[level]
self.logger.setLevel(lvl)
if lvl > logging.DEBUG:
LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s.%(funcName)s: %(message)s'
else:
LOG_FORMAT = (
'-' * 80 + '\n' +
'%(levelname)s in %(module)s.%(funcName)s [%(pathname)s:%(lineno)d]:\n' +
'%(message)s\n' +
'-' * 80
)
if logfile:
handler = RotatingFileHandler(logfile, maxBytes=1024 * 1024 * 100, backupCount=20)
else:
handler = logging.StreamHandler()
handler.setLevel(lvl)
handler.setFormatter(logging.Formatter(LOG_FORMAT))
self.logger.addHandler(handler)
self._logger('info', 'conf: ' + conf)
self._logger('info', 'level: ' + logging.getLevelName(lvl))
if not conf:
raise IOError('No configuration file found')
# Raise exception if errors are encountered during parsing
self.conf = config
self.conf.parse(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.conf.setdefault('BUI_AGENT', True)
self.client = BurpHandler(self.vers, self.logger, self.conf)
pool = Pool(10000)
if not self.ssl:
self.server = StreamServer((self.bind, self.port), self.handle, spawn=pool)
else:
self.server = StreamServer((self.bind, self.port), self.handle, keyfile=self.sslkey, certfile=self.sslcert, spawn=pool)
def run(self):
try:
self.server.serve_forever()
except KeyboardInterrupt:
sys.exit(0)
def handle(self, request, address):
"""self.request is the client connection"""
with lock:
try:
self.request = request
err = None
res = ''
lengthbuf = self.request.recv(8)
if not lengthbuf:
return
length, = struct.unpack('!Q', lengthbuf)
data = self.recvall(length)
self._logger('info', 'recv: {}'.format(data))
txt = to_unicode(data)
self._logger('info', 'recv2: {}'.format(txt))
if txt == 'RE':
return
j = json.loads(txt)
if j['password'] != self.password:
self._logger('warning', '-----> Wrong Password <-----')
self.request.sendall(b'KO')
return
try:
if j['func'] == 'proxy_parser':
parser = self.client.get_parser()
if j['args']:
res = json.dumps(getattr(parser, j['method'])(**j['args']))
else:
res = json.dumps(getattr(parser, j['method'])())
elif j['func'] == 'agent_version':
res = json.dumps(__version__)
elif j['func'] == 'restore_files':
res, err = getattr(self.client, j['func'])(**j['args'])
if err:
self.request.sendall(b'ER')
self.request.sendall(struct.pack('!Q', len(err)))
self.request.sendall(to_bytes(err))
self._logger('error', 'Restoration failed')
return
elif j['func'] == 'get_file':
path = j['path']
path = os.path.normpath(path)
err = None
if not path.startswith('/'):
err = 'The path must be absolute! ({})'.format(path)
if not path.startswith(self.client.tmpdir):
err = 'You are not allowed to access this path: ' \
'({})'.format(path)
if err:
self.request.sendall(b'ER')
self.request.sendall(struct.pack('!Q', len(err)))
self.request.sendall(to_bytes(err))
self._logger('error', err)
return
size = os.path.getsize(path)
self.request.sendall(b'OK')
self.request.sendall(struct.pack('!Q', size))
with open(path, 'rb') as f:
if not USE_SENDFILE:
while True:
buf = f.read(1024)
if not buf:
break
self._logger('info', 'sending {} Bytes'.format(len(buf)))
self.request.sendall(buf)
else:
offset = 0
while True:
sent = sendfile(self.request.fileno(), f.fileno(), offset, size)
if sent == 0:
break
offset += sent
os.unlink(path)
lengthbuf = self.request.recv(8)
length, = struct.unpack('!Q', lengthbuf)
data = self.recvall(length)
txt = to_unicode(data)
if txt == 'RE':
return
elif j['func'] == 'del_file':
path = j['path']
path = os.path.normpath(path)
err = None
if not path.startswith('/'):
err = 'The path must be absolute! ({})'.format(path)
if not path.startswith(self.client.tmpdir):
err = 'You are not allowed to access this path: ' \
'({})'.format(path)
if err:
self.request.sendall(b'ER')
self.request.sendall(struct.pack('!Q', len(err)))
self.request.sendall(to_bytes(err))
self._logger('error', err)
return
res = json.dumps(False)
if os.path.isfile(path):
os.unlink(path)
res = json.dumps(True)
else:
if j['args']:
if 'pickled' in j and j['pickled']:
# de-serialize arguments if needed
import hmac
import hashlib
from base64 import b64decode
pickles = to_bytes(j['args'])
key = u'{}{}'.format(self.password, j['func'])
key = to_bytes(key)
bytes_pickles = pickles
digest = hmac.new(key, bytes_pickles, hashlib.sha1).hexdigest()
if digest != j['digest']:
raise BUIserverException('Integrity check failed: {} != {}'.format(digest, j['digest']))
# We need to replace the burpui datastructure
# module by our own since it's the same but
# burpui may not be installed
mod = __name__
if '.' in mod:
mod = mod.split('.')[0]
data = b64decode(pickles)
data = data.replace(b'burpui.datastructures', to_bytes('{}.datastructures'.format(mod)))
j['args'] = pickle.loads(data)
res = json.dumps(getattr(self.client, j['func'])(**j['args']))
else:
res = json.dumps(getattr(self.client, j['func'])())
self._logger('info', 'result: {}'.format(res))
self.request.sendall(b'OK')
except (BUIserverException, Exception) as e:
self.request.sendall(b'ER')
res = str(e)
self._logger('error', traceback.format_exc())
self._logger('warning', 'Forwarding Exception: {}'.format(res))
self.request.sendall(struct.pack('!Q', len(res)))
self.request.sendall(to_bytes(res))
return
self.request.sendall(struct.pack('!Q', len(res)))
self.request.sendall(to_bytes(res))
except AttributeError as e:
self._logger('warning', '{}\nWrong method => {}'.format(traceback.format_exc(), str(e)))
self.request.sendall(b'KO')
except Exception as e:
self._logger('error', '!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
finally:
try:
self.request.close()
except Exception as e:
self._logger('error', '!!! {} !!!\n{}'.format(str(e), traceback.format_exc()))
def recvall(self, length=1024):
buf = b''
bsize = 1024
received = 0
if length < bsize:
bsize = length
while received < length:
newbuf = self.request.recv(bsize)
if not newbuf:
time.sleep(0.1)
continue
buf += newbuf
received += len(newbuf)
return buf
def _logger(self, level, message):
# hide password from logs
msg = message
if not self.logger:
return
if self.logger.getEffectiveLevel() != DISCLOSURE:
msg = re.sub(r'([\'"])password\1(\s*:\s*)([\'"])[^\3]+?\3', r'\1password\1\2\3*****\3', message)
super(BUIAgent, self)._logger(level, msg)
......@@ -8,41 +8,42 @@
"""
import hashlib
import os
import sys
import uuid
import hashlib
import logging
from functools import wraps
from importlib import import_module
from flask import Blueprint, Response, request, current_app, session, abort
from flask_restplus import Api as ApiPlus
from flask import Blueprint, Response, abort, current_app, g, request, session
from flask_login import current_user
from importlib import import_module
from functools import wraps
from flask_restx import Api as ApiPlus
from .custom.namespace import Namespace
from .._compat import to_bytes
from ..desc import __version__, __release__, __url__, __doc__
from ..server import BUIServer # noqa
from ..exceptions import BUIserverException
from ..config import config
from ..desc import __doc__, __release__, __url__, __version__
from ..engines.server import BUIServer # noqa
from ..exceptions import BUIserverException
from ..tools.logging import logger
bui = current_app # type: BUIServer
EXEMPT_METHODS = set(['OPTIONS'])
EXEMPT_METHODS = set(["OPTIONS"])
def force_refresh():
return request.headers.get('X-No-Cache', False) is not False
return request.headers.get("X-No-Cache", False) is not False or getattr(
g, "DONOTCACHE", False
)
def cache_key():
key = '{}-{}-{}-{}-{}-{}'.format(
session.get('login', uuid.uuid4()),
key = "{}-{}-{}-{}-{}-{}".format(
session.get("login", uuid.uuid4()),
request.path,
request.values,
request.headers.get('X-Session-Tag', ''),
request.headers.get("X-Session-Tag", ""),
request.cookies,
session.get('language', '')
session.get("language", ""),
)
key = hashlib.sha256(to_bytes(key)).hexdigest()
return key
......@@ -52,6 +53,7 @@ def api_login_required(func):
"""Custom login decorator that is able to parse Basic credentials as well as
Cookies set with the traditional login.
"""
@wraps(func)
def decorated_view(*args, **kwargs):
"""decorator"""
......@@ -60,23 +62,29 @@ def api_login_required(func):
# 'func' is a Flask.view.MethodView so we have access to some special
# params
cls = func.view_class
login_required = getattr(cls, 'login_required', True)
if (bui.auth != 'none' and
login_required and
not bui.config.get('LOGIN_DISABLED', False)):
login_required = getattr(cls, "login_required", True)
if (
bui.auth != "none"
and login_required
and not bui.config.get("LOGIN_DISABLED", False)
):
if not current_user.is_authenticated:
if request.headers.get('X-From-UI', False):
if request.headers.get("X-From-UI", False):
abort(403)
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
"Could not verify your access level for that URL.\n"
"You have to login with proper credentials",
401,
{"WWW-Authenticate": 'Basic realm="Login Required"'},
)
return func(*args, **kwargs)
return decorated_view
def check_acl(func):
"""Custom decorator to check if the ACL are in use or not"""
@wraps(func)
def decorated_view(*args, **kwargs):
if request.method in EXEMPT_METHODS: # pragma: no cover
......@@ -84,129 +92,156 @@ def check_acl(func):
# 'func' is a Flask.view.MethodView so we have access to some special
# params
cls = func.view_class
login_required = getattr(cls, 'login_required', True)
if (bui.auth != 'none' and
login_required and
not bui.config.get('LOGIN_DISABLED', False)):
login_required = getattr(cls, "login_required", True)
if (
bui.auth != "none"
and login_required
and not bui.config.get("LOGIN_DISABLED", False)
):
if current_user.is_anonymous:
abort(403)
return func(*args, **kwargs)
return decorated_view
class Api(ApiPlus):
"""Wrapper class around :class:`flask_restplus.Api`"""
logger = logging.getLogger('burp-ui')
"""Wrapper class around :class:`flask_restx.Api`"""
logger = logger
# TODO: should use global object instead of reference
loaded = False
release = __release__
__doc__ = __doc__
__url__ = __url__
CELERY_REQUIRED = ['async']
CELERY_REQUIRED = ["tasks"]
def load_all(self):
if config['WITH_LIMIT']:
if config["WITH_LIMIT"]:
try:
from ..ext.limit import limiter
self.decorators.append(limiter.limit(config['BUI_RATIO']))
self.decorators.append(limiter.limit(config["BUI_RATIO"]))
except ImportError:
self.logger.warning('Unable to import limiter module')
self.logger.warning("Unable to import limiter module")
"""hack to automatically import api modules"""
if not self.loaded:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
self.loaded = True
for f in os.listdir(__path__[0]):
name, ext = os.path.splitext(f)
if (os.path.isfile(os.path.join(__path__[0], f)) and
ext == '.py' and
name not in ['__init__', '.', '..']):
mod = '.' + name
if name not in self.CELERY_REQUIRED or \
config['WITH_CELERY']:
self.logger.debug('Loading API module: {}'.format(mod))
if (
os.path.isfile(os.path.join(__path__[0], f))
and ext == ".py"
and name not in ["__init__", ".", ".."]
):
mod = "." + name
if name not in self.CELERY_REQUIRED or config["WITH_CELERY"]:
self.logger.debug("Loading API module: {}".format(mod))
try:
import_module(mod, __name__)
except: # pragma: no cover
import traceback
except Exception as exc: # pragma: no cover
self.logger.critical(
'Unable to load {}:\n{}'.format(
"Unable to load {}:\n{}".format(
mod,
traceback.format_exc()
)
exc,
),
exc_info=exc,
stack_info=True,
)
else:
self.logger.warning(
'Skipping API module: {}'.format(mod)
)
self.logger.warning("Skipping API module: {}".format(mod))
def acl_admin_required(self, message='Access denied', code=403):
def acl_admin_required(self, message="Access denied", code=403):
def decorator(func):
@wraps(func)
def decorated(resource, *args, **kwargs):
if not current_user.is_anonymous and \
not current_user.acl.is_admin():
if not current_user.is_anonymous and not current_user.acl.is_admin():
resource.abort(code, message)
return func(resource, *args, **kwargs)
return decorated
return decorator
def acl_admin_or_moderator_required(self, message='Access denied', code=403):
def acl_admin_or_moderator_required(self, message="Access denied", code=403):
def decorator(func):
@wraps(func)
def decorated(resource, *args, **kwargs):
if not current_user.is_anonymous and \
not current_user.acl.is_admin() and \
not current_user.acl.is_moderator():
if (
not current_user.is_anonymous
and not current_user.acl.is_admin()
and not current_user.acl.is_moderator()
):
resource.abort(code, message)
return func(resource, *args, **kwargs)
return decorated
return decorator
def acl_own_or_admin(self, key='name', message='Access denied', code=403):
def acl_own_or_admin(self, key="name", message="Access denied", code=403):
def decorator(func):
@wraps(func)
def decorated(resource, *args, **kwargs):
if key not in kwargs: # pragma: no cover
resource.abort(500, "key '{}' not found".format(key))
if kwargs[key] != current_user.name and \
not current_user.is_anonymous and \
not current_user.acl.is_admin():
if (
kwargs[key] != current_user.name
and not current_user.is_anonymous
and not current_user.acl.is_admin()
):
resource.abort(code, message)
return func(resource, *args, **kwargs)
return decorated
return decorator
def acl_own_or_admin_or_moderator(
self, key="name", message="Access denied", code=403
):
def decorator(func):
@wraps(func)
def decorated(resource, *args, **kwargs):
if key not in kwargs: # pragma: no cover
resource.abort(500, "key '{}' not found".format(key))
if (
kwargs[key] != current_user.name
and not current_user.is_anonymous
and not current_user.acl.is_admin()
and not current_user.acl.is_moderator()
):
resource.abort(code, message)
return func(resource, *args, **kwargs)
return decorated
return decorator
def disabled_on_demo(self):
def decorator(func):
@wraps(func)
def decorated(resource, *args, **kwargs):
if config['BUI_DEMO']:
if config["BUI_DEMO"]:
resource.abort(
405,
'Sorry, this feature is not available on the demo'
405, "Sorry, this feature is not available on the demo"
)
return func(resource, *args, **kwargs)
return decorated
return decorator
def namespace(self, *args, **kwargs):
"""A namespace factory
return decorated
:returns Namespace: a new namespace instance
"""
ns = Namespace(*args, **kwargs)
self.add_namespace(ns)
return ns
return decorator
apibp = Blueprint('api', __name__, url_prefix='/api')
apibp = Blueprint("api", __name__, url_prefix="/api")
api = Api(
apibp,
title='Burp-UI API',
description='Burp-UI API to interact with burp',
title="Burp-UI API",
description="Burp-UI API to interact with burp",
version=__version__,
doc='/doc',
decorators=[api_login_required]
doc="/doc",
decorators=[api_login_required],
)
......@@ -217,4 +252,5 @@ def handle_bui_server_exception(error):
:param error: Custom exception
:type error: :class:`burpui.exceptions.BUIserverException`
"""
return {'message': error.description}, error.code
bui.logger.error(error)
return {"message": error.description}, error.code
This diff is collapsed.
......@@ -7,30 +7,34 @@
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
from . import api
from ..server import BUIServer # noqa
from .custom import Resource
from ..exceptions import BUIserverException
from flask import current_app
from flask_login import current_user
from ..engines.server import BUIServer # noqa
from ..exceptions import BUIserverException
from . import api
from .custom import Resource
bui = current_app # type: BUIServer
ns = api.namespace('backup', 'Backup methods')
ns = api.namespace("backup", "Backup methods")
@ns.route('/server-backup/<name>',
'/<server>/server-backup/<name>',
methods=['GET', 'DELETE'],
endpoint='is_server_backup')
@ns.route('/do-server-backup/<name>',
'/<server>/do-server-backup/<name>',
methods=['PUT'],
endpoint='server_backup')
@ns.route(
"/server-backup/<name>",
"/<server>/server-backup/<name>",
methods=["GET", "DELETE"],
endpoint="is_server_backup",
)
@ns.route(
"/do-server-backup/<name>",
"/<server>/do-server-backup/<name>",
methods=["PUT"],
endpoint="server_backup",
)
@ns.doc(
params={
'server': 'Which server to collect data from when in multi-agent mode',
'name': 'Client name',
"server": "Which server to collect data from when in multi-agent mode",
"name": "Client name",
},
)
class ServerBackup(Resource):
......@@ -39,12 +43,13 @@ class ServerBackup(Resource):
This resource is part of the :mod:`burpui.api.backup` module.
"""
@ns.doc(
responses={
200: 'Success',
400: 'Missing parameter',
403: 'Insufficient permissions',
500: 'Internal failure',
200: "Success",
400: "Missing parameter",
403: "Insufficient permissions",
500: "Internal failure",
},
)
def get(self, server=None, name=None):
......@@ -62,23 +67,25 @@ class ServerBackup(Resource):
:returns: True if the file is found
"""
if not name:
self.abort(400, 'Missing options')
self.abort(400, "Missing options")
# Manage ACL
if not current_user.is_anonymous and \
not current_user.acl.is_admin() and \
not current_user.acl.is_client_allowed(name, server):
self.abort(403, 'You are not allowed to access this client')
if (
not current_user.is_anonymous
and not current_user.acl.is_admin()
and not current_user.acl.is_client_allowed(name, server)
):
self.abort(403, "You are not allowed to access this client")
try:
return {'is_server_backup': bui.client.is_server_backup(name, server)}
return {"is_server_backup": bui.client.is_server_backup(name, server)}
except BUIserverException as e:
self.abort(500, str(e))
@ns.doc(
responses={
200: 'Success',
400: 'Missing parameter',
403: 'Insufficient permissions',
500: 'Internal failure',
200: "Success",
400: "Missing parameter",
403: "Insufficient permissions",
500: "Internal failure",
},
)
def delete(self, server=None, name=None):
......@@ -96,24 +103,30 @@ class ServerBackup(Resource):
:returns: Status message (success or failure)
"""
if not name:
self.abort(400, 'Missing options')
self.abort(400, "Missing options")
# Manage ACL
if not current_user.is_anonymous and \
not current_user.acl.is_admin() and \
not current_user.acl.is_moderator() and \
not current_user.acl.is_client_rw(name, server):
self.abort(403, 'You are not allowed to cancel a backup for this client')
if (
not current_user.is_anonymous
and not current_user.acl.is_admin()
and not current_user.acl.is_moderator()
and not current_user.acl.is_client_rw(name, server)
):
self.abort(403, "You are not allowed to cancel a backup for this client")
try:
bui.audit.logger.info(
f"requested server-initiated backup cancellation of {name}",
server=server,
)
return bui.client.cancel_server_backup(name, server)
except BUIserverException as e:
self.abort(500, str(e))
@ns.doc(
responses={
201: 'Success',
400: 'Missing parameter',
403: 'Insufficient permissions',
500: 'Internal failure',
201: "Success",
400: "Missing parameter",
403: "Insufficient permissions",
500: "Internal failure",
},
)
def put(self, server=None, name=None):
......@@ -133,17 +146,20 @@ class ServerBackup(Resource):
json = []
# Check params
if not name:
self.abort(400, 'Missing options')
self.abort(400, "Missing options")
# Manage ACL
if not current_user.is_anonymous and \
not current_user.acl.is_admin() and \
not current_user.acl.is_moderator() and \
not current_user.acl.is_client_rw(name, server):
self.abort(
403,
'You are not allowed to schedule a backup for this client'
)
if (
not current_user.is_anonymous
and not current_user.acl.is_admin()
and not current_user.acl.is_moderator()
and not current_user.acl.is_client_rw(name, server)
):
self.abort(403, "You are not allowed to schedule a backup for this client")
try:
bui.audit.logger.info(
f"requested server-initiated backup scheduling for {name}",
server=server,
)
json = bui.client.server_backup(name, server)
return json, 201
except BUIserverException as e:
......
This diff is collapsed.