mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-21 06:45:24 -06:00
add: limiter extension to allow rate-limit the API
This commit is contained in:
parent
3692887d80
commit
12100ff884
9 changed files with 154 additions and 20 deletions
|
|
@ -71,6 +71,9 @@ class Api(ApiPlus):
|
|||
CELERY_REQUIRED = ['async']
|
||||
|
||||
def load_all(self):
|
||||
if config['WITH_LIMIT']:
|
||||
from ..ext.limit import limiter
|
||||
self.decorators.append(limiter.limit(config['BUI_RATIO']))
|
||||
"""hack to automatically import api modules"""
|
||||
if not self.loaded:
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
|
|
|||
|
|
@ -337,7 +337,6 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
list=list,
|
||||
mypad=mypad,
|
||||
version_id='{}-{}'.format(__version__, __release__),
|
||||
config=app.config
|
||||
)
|
||||
|
||||
# manage application secret key
|
||||
|
|
@ -358,7 +357,7 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||
|
||||
if app.storage and app.storage.lower() != 'default':
|
||||
if app.storage and app.storage.lower() == 'redis':
|
||||
try:
|
||||
# Session setup
|
||||
if not app.session_db or \
|
||||
|
|
@ -378,10 +377,11 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
db = int(db)
|
||||
except ValueError:
|
||||
db = 0
|
||||
logger.debug('Using redis://guest:****@{}:{}/{}'.format(
|
||||
host,
|
||||
port,
|
||||
db)
|
||||
logger.debug(
|
||||
'SESSION: Using redis://guest:****@{}:{}/{}'.format(
|
||||
host,
|
||||
port,
|
||||
db)
|
||||
)
|
||||
red = Redis(host=host, port=port, db=db, password=pwd)
|
||||
app.config['SESSION_TYPE'] = 'redis'
|
||||
|
|
@ -390,6 +390,9 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
app.config['SESSION_PERMANENT'] = False
|
||||
sess.init_app(app)
|
||||
session_manager.backend = red
|
||||
except Exception as exp:
|
||||
logger.warning('Unable to initialize session: {}'.format(str(exp)))
|
||||
try:
|
||||
# Cache setup
|
||||
if not app.cache_db or \
|
||||
app.cache_db.lower() not in ['none']:
|
||||
|
|
@ -406,7 +409,7 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
db = int(db)
|
||||
except ValueError:
|
||||
db = 1
|
||||
logger.debug('Using redis://guest:****@{}:{}/{}'.format(
|
||||
logger.debug('CACHE: Using redis://guest:****@{}:{}/{}'.format(
|
||||
host,
|
||||
port,
|
||||
db)
|
||||
|
|
@ -426,9 +429,48 @@ def create_app(conf=None, verbose=0, logfile=None, **kwargs):
|
|||
cache.clear()
|
||||
else:
|
||||
cache.init_app(app)
|
||||
except Exception as e:
|
||||
logger.warning('Unable to initialize redis: {}'.format(str(e)))
|
||||
except Exception as exp:
|
||||
logger.warning('Unable to initialize cache: {}'.format(str(exp)))
|
||||
cache.init_app(app)
|
||||
try:
|
||||
# Limiter setup
|
||||
if not app.limiter or app.limiter.lower() not in ['none']:
|
||||
from .ext.limit import limiter
|
||||
app.config['RATELIMIT_HEADERS_ENABLED'] = True
|
||||
if app.limiter and app.limiter not in ['default', 'redis']:
|
||||
app.config['RATELIMIT_STORAGE_URL'] = app.limiter
|
||||
else:
|
||||
db = 3
|
||||
host, port, pwd = get_redis_server(app)
|
||||
if pwd:
|
||||
conn = 'redis://guest:{}@{}:{}/{}'.format(
|
||||
pwd,
|
||||
host,
|
||||
port,
|
||||
db
|
||||
)
|
||||
else:
|
||||
conn = 'redis://{}:{}/{}'.format(host, port, db)
|
||||
app.config['RATELIMIT_STORAGE_URL'] = conn
|
||||
|
||||
(_, _, pwd, host, port, db) = parse_db_setting(
|
||||
app.config['RATELIMIT_STORAGE_URL']
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
'LIMITER: Using redis://guest:****@{}:{}/{}'.format(
|
||||
host,
|
||||
port,
|
||||
db
|
||||
)
|
||||
)
|
||||
limiter.init_app(app)
|
||||
app.config['WITH_LIMIT'] = True
|
||||
except ImportError:
|
||||
logger.warning('Unable to load limiter. Did you run \'pip install '
|
||||
'flask-limiter\'?')
|
||||
except Exception as exp:
|
||||
logger.warning('Unable to initialize limiter: {}'.format(str(exp)))
|
||||
else:
|
||||
cache.init_app(app)
|
||||
|
||||
|
|
|
|||
13
burpui/ext/limit.py
Normal file
13
burpui/ext/limit.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf8 -*-
|
||||
"""
|
||||
.. module:: burpui.ext.limit
|
||||
:platform: Unix
|
||||
:synopsis: Burp-UI external Limit module.
|
||||
|
||||
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
|
||||
|
||||
"""
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
|
@ -15,7 +15,7 @@ from wtforms import TextField, PasswordField, BooleanField, SelectField, validat
|
|||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = TextField(__('Username'), [validators.Length(min=2, max=25)])
|
||||
username = TextField(__('Username'), [validators.Required()])
|
||||
password = PasswordField(__('Password'), [validators.Required()])
|
||||
language = SelectField(__('Language'), choices=LANGUAGES.items(), default=get_locale)
|
||||
remember = BooleanField(__('Remember me'), [validators.Optional()])
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ G_STORAGE = u''
|
|||
G_CACHE = u''
|
||||
G_SESSION = u''
|
||||
G_REDIS = u''
|
||||
G_LIMITER = False
|
||||
G_RATIO = u'20/minute'
|
||||
G_CELERY = False
|
||||
G_SCOOKIE = True
|
||||
G_DEMO = False
|
||||
|
|
@ -82,6 +84,8 @@ class BUIServer(Flask):
|
|||
'redis': G_REDIS,
|
||||
'celery': G_CELERY,
|
||||
'database': G_DATABASE,
|
||||
'limiter': G_LIMITER,
|
||||
'ratio': G_RATIO,
|
||||
},
|
||||
'Experimental': {
|
||||
'noserverrestore': G_NO_SERVER_RESTORE,
|
||||
|
|
@ -94,19 +98,23 @@ class BUIServer(Flask):
|
|||
|
||||
:param app: The Flask application to launch
|
||||
"""
|
||||
super(BUIServer, self).__init__('burpui')
|
||||
self.init = False
|
||||
# We cannot override the Flask's logger so we use our own
|
||||
self.builogger = logging.getLogger('burp-ui')
|
||||
self.builogger.disabled = True
|
||||
super(BUIServer, self).__init__('burpui')
|
||||
self._logger = logging.getLogger('burp-ui')
|
||||
self._logger.disabled = True
|
||||
self.conf = config
|
||||
# switch the flask config with our magic config object
|
||||
self.conf.update(self.config)
|
||||
self.config = self.conf
|
||||
|
||||
def enable_logger(self, enable=True):
|
||||
"""Enable or disable the logger"""
|
||||
self.builogger.disabled = not enable
|
||||
self._logger.disabled = not enable
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return self.builogger
|
||||
return self._logger
|
||||
|
||||
def setup(self, conf=None, unittest=False, cli=False):
|
||||
"""The :func:`burpui.server.BUIServer.setup` functions is used to setup
|
||||
|
|
@ -131,14 +139,9 @@ class BUIServer(Flask):
|
|||
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')
|
||||
|
||||
# switch the flask config with our magic config object
|
||||
self.conf.update(self.config)
|
||||
self.config = self.conf
|
||||
|
||||
self.port = self.config['BUI_PORT'] = self.conf.safe_get(
|
||||
'port',
|
||||
'integer'
|
||||
|
|
@ -208,6 +211,17 @@ class BUIServer(Flask):
|
|||
'redis',
|
||||
section='Production'
|
||||
)
|
||||
self.limiter = self.config['BUI_LIMITER'] = self.conf.safe_get(
|
||||
'limiter',
|
||||
'boolean_or_string',
|
||||
section='Production'
|
||||
)
|
||||
if isinstance(self.limiter, bool) and not self.limiter:
|
||||
self.limiter = self.config['BUI_LIMITER'] = 'none'
|
||||
self.ratio = self.config['BUI_RATIO'] = self.conf.safe_get(
|
||||
'ratio',
|
||||
section='Production'
|
||||
)
|
||||
self.use_celery = self.config['BUI_CELERY'] = self.conf.safe_get(
|
||||
'celery',
|
||||
'boolean_or_string',
|
||||
|
|
@ -219,6 +233,7 @@ class BUIServer(Flask):
|
|||
'boolean_or_string',
|
||||
section='Production'
|
||||
)
|
||||
self.config['WITH_LIMIT'] = False
|
||||
if isinstance(self.database, bool):
|
||||
self.config['WITH_SQL'] = self.database
|
||||
else:
|
||||
|
|
|
|||
29
docs/faq.rst
29
docs/faq.rst
|
|
@ -105,12 +105,41 @@ Are there any known issues?
|
|||
There is a `known issue <introduction.html#known-issues>`__ section in this
|
||||
documentation.
|
||||
|
||||
Why using redis?
|
||||
----------------
|
||||
|
||||
Redis may be used for several things:
|
||||
|
||||
- store the sessions server side (by default sessions are stored client side in
|
||||
a secure cookie)
|
||||
- cache some data
|
||||
- monitor API usage for the rate limiter
|
||||
|
||||
All of these features are totally optional.
|
||||
Redis is also used by celery to interact between Burp-UI and the asynchronous
|
||||
worker.
|
||||
|
||||
Why using SQL?
|
||||
--------------
|
||||
|
||||
The SQL database is currently used to keep a track of several meta-data.
|
||||
Again, it is totally optional to use it.
|
||||
|
||||
Burp-UI does not work anymore since I upgraded it, what can I do?
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Make sure you read the `upgrading <upgrading.html>`_ page in case some breaking
|
||||
changes occurred.
|
||||
|
||||
I am getting errors while restoring large files (>3GB), what should I do?
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
The default *zip* module does not support large files by default. You can either
|
||||
enable large file support by setting ``zip64 = true`` in the ``[Experimental]``
|
||||
section.
|
||||
Alternatively, you can choose an other compression module by selecting an other
|
||||
extension while proceeding the restoration.
|
||||
|
||||
How can I contribute?
|
||||
---------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,16 @@ If you need persistent data, you will need additional dependencies as well:
|
|||
pip install burp-ui-sql
|
||||
|
||||
|
||||
Limiter
|
||||
-------
|
||||
|
||||
If you want to rate-limit the API, you will need additional dependencies too:
|
||||
|
||||
::
|
||||
|
||||
pip install flask-limiter
|
||||
|
||||
|
||||
Burp1
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,17 @@ follow:
|
|||
# http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls
|
||||
# example: sqlite:////var/lib/burpui/store.db
|
||||
database = none
|
||||
# whether to rate limit the API or not
|
||||
# may also be a redis url like: redis://localhost:6379/0
|
||||
# if set to "true" or "redis" or "default", the url defaults to:
|
||||
# redis://<redis_host>:<redis_port>/3
|
||||
# where <redis_host> is the host part, and <redis_port> is the port part of
|
||||
# the above "redis" setting
|
||||
# Note: the limiter only applies to the API routes
|
||||
limiter = false
|
||||
# limiter ratio
|
||||
# see https://flask-limiter.readthedocs.io/en/stable/#ratelimit-string
|
||||
ratio = 20/minute
|
||||
|
||||
|
||||
Experimental
|
||||
|
|
|
|||
|
|
@ -63,6 +63,17 @@ celery = false
|
|||
# http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls
|
||||
# example: sqlite:////var/lib/burpui/store.db
|
||||
database = none
|
||||
# whether to rate limit the API or not
|
||||
# may also be a redis url like: redis://localhost:6379/0
|
||||
# if set to "true" or "redis" or "default", the url defaults to:
|
||||
# redis://<redis_host>:<redis_port>/3
|
||||
# where <redis_host> is the host part, and <redis_port> is the port part of
|
||||
# the above "redis" setting
|
||||
# Note: the limiter only applies to the API routes
|
||||
limiter = false
|
||||
# limiter ratio
|
||||
# see https://flask-limiter.readthedocs.io/en/stable/#ratelimit-string
|
||||
ratio = 20/minute
|
||||
|
||||
[Security]
|
||||
## This section contains some security options. Make sure you understand the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue