diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d4e63ca8..86d2541a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Current - Add: new ``bui-monitor`` processes pool + ``async`` backend to parallelize some requests `#278 `_ - Add: new `listen` and `listen_status` options in burp-2.2.10 `#279 `_ - Add: new `order` keyword in ACL definitions in order to decide whether `rw` should be evaluated first or not `#305 `__ +- Add: new `exclude` keyword in ACL definitions in order to exclude some clients from the rules `#305 `__ - Add: allow to hide selected clients/servers `#282 `_ - Add: allow to delete clients data upon removal `#232 `_ - Add: allow to create clients from templates in one call `#266 `_ diff --git a/burpui/misc/acl/meta.py b/burpui/misc/acl/meta.py index ff269381..756e9543 100644 --- a/burpui/misc/acl/meta.py +++ b/burpui/misc/acl/meta.py @@ -16,25 +16,37 @@ import re import json import fnmatch -PARSE_EXCLUDE_KEYS = ['agents', 'clients', 'ro', 'rw', 'order'] -PARSE_RESERVED_KEYS = ['ro', 'rw', 'order'] -DEFAULT_EVAL_ORDER = ['rw', 'ro'] +PARSE_EXCLUDE_KEYS = ['agents', 'clients', 'ro', 'rw', 'order', 'exclude'] +PARSE_RESERVED_KEYS = ['ro', 'rw', 'order', 'exclude'] +DEFAULT_EVAL_ORDER = ['exclude', 'rw', 'ro'] MODE_RETURN = { 'ro': False, 'rw': True, } -def _get_order(data, key): +def _extract_key(data, key, name, default=[], fallback='clients'): if not isinstance(data, dict): - return DEFAULT_EVAL_ORDER - order = data.get('order', {}) - if key in order: - return order[key] - elif 'clients' in order: - return order['clients'] - else: - return DEFAULT_EVAL_ORDER + return default + + ret = None + extract = data.get(key, {}) + + if isinstance(name, list): + for nm in name: + if nm in extract: + ret = make_list(extract[nm]) + elif name: + if name in extract: + ret = make_list(extract[name]) + + if ret: + if key == 'order': + for odr in DEFAULT_EVAL_ORDER: + if odr not in ret: + ret.append(odr) + return ret + return extract.get(fallback, default) class BUImetaGrant(object): @@ -115,7 +127,7 @@ class BUImetaGrant(object): par = key cl2, ag2, ad2 = self._parse_clients(data[key], md, parent=par) agents = self._merge_data(agents, ag2) - if not md or md not in ['order']: + if not md or md not in ['order', 'exclude']: clients = self._merge_data(clients, cl2) if parent and parent not in PARSE_EXCLUDE_KEYS: ro = ad2.get('ro') @@ -445,7 +457,7 @@ class BUIgrantHandler(BUImetaGrant, BUIacl): return is_admin or self.opt('assume_rw', True) for adv in advanced: - order = _get_order(adv, server) + order = _extract_key(adv, 'order', [server] + server_match, DEFAULT_EVAL_ORDER) for adv2 in advanced: # the whole agent is rw and we did not find explicit entry for # client_match @@ -489,10 +501,10 @@ class BUIgrantHandler(BUImetaGrant, BUIacl): for adv in advanced: if server: - key = server + key = [server] + self._server_match(username, server) else: - key = 'clients' - order = _get_order(adv, key) + key = None + order = _extract_key(adv, 'order', key, DEFAULT_EVAL_ORDER) for odr in order: eval_clients = adv.get(odr, {}).get('clients', []) @@ -531,29 +543,46 @@ class BUIgrantHandler(BUImetaGrant, BUIacl): if advanced is not False: for idx, adv in enumerate(advanced): - if all(x not in adv for x in server_match) and \ - any(x in y - for x in server_match - for y in self._extract_advanced_mode(username, 'ro', 'agents', idx) - ) or \ - any(x in y - for x in server_match - for y in self._extract_advanced_mode(username, 'rw', 'agents', idx) - ): - return True + order = _extract_key(adv, 'order', [server] + server_match, DEFAULT_EVAL_ORDER) + excludes = _extract_key(adv, 'exclude', [server] + server_match, fallback='agents') + if all(x not in adv for x in server_match): + for odr in order: + if odr == 'exclude' and ( + any(x in excludes for x in client_match) or + client in excludes): + return False + elif any(x in y + for x in server_match + for y in self._extract_advanced_mode(username, odr, 'agents', idx) + ): + return True tmp = set(adv.get(server, [])) for srv in server_match: tmp |= set(adv.get(srv, [])) adv2 = list(tmp) - if client_match is not False and \ - (any(x in adv2 for x in client_match) or - client in adv2): - return True + excludes = _extract_key(adv, 'exclude', [server] + server_match) + for odr in order: + if odr == 'exclude' and ( + any(x in excludes for x in client_match) or + client in excludes): + return False + elif client_match is not False and \ + (any(x in adv2 for x in client_match) or + client in adv2): + return True return False - return client_match is not False or is_admin + order = _extract_key(adv, 'order', None, DEFAULT_EVAL_ORDER) + excludes = _extract_key(adv, 'exclude', None) + + for odr in order: + if odr == 'exclude' and client_match and ( + any(x in excludes for x in client_match) or + client in excludes): + return False + return client_match is not False or is_admin def is_server_rw(self, username=None, server=None): """See :func:`burpui.misc.acl.interface.BUIacl.is_server_rw`""" @@ -576,7 +605,7 @@ class BUIgrantHandler(BUImetaGrant, BUIacl): advanced = self._extract_advanced(username) for adv in advanced: - order = _get_order(adv, server) + order = _extract_key(adv, 'order', [server] + server_match, DEFAULT_EVAL_ORDER) for odr in order: if any(x in adv.get(odr, {}).get('agents', []) for x in server_match): return MODE_RETURN.get(odr, False) diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst index 7c784f9c..05d8c78b 100644 --- a/docs/advanced_usage.rst +++ b/docs/advanced_usage.rst @@ -801,6 +801,33 @@ With the above rule, the engine will treat ``client.specific.test`` as ``ro`` whereas without the ``order`` keywoard, ``client.specific.test`` would have matched the ``rw`` rule first and thus would be considered as ``rw``. +There is also a new ``exclude`` keyword that supports excluding clients from +the matching rules. + +Here is an example: + +:: + + # rule is: myuser = '{"agents": {"agent1": {"exclude": ["client.test1"], "ro": ["client.specific.*"], "rw": ["client.*", "server.*"]}, "agent2": {"rw": ["client.*"]}}}' + + In [3]: meta_grants.is_client_rw('myuser', 'client.specific.test1', 'agent1') + Out[3]: False + + In [4]: meta_grants.is_client_rw('myuser', 'client.test1', 'agent1') + Out[4]: False + + In [5]: meta_grants.is_client_rw('myuser', 'client.test2', 'agent1') + Out[5]: True + + In [6]: meta_grants.is_client_rw('myuser', 'client.test1', 'agent2') + Out[6]: True + + In [7]: meta_grants.is_client_allowed('myuser', 'client.test1', 'agent1') + Out[7]: False + + In [8]: meta_grants.is_client_allowed('myuser', 'client.specific.test1', 'agent1') + Out[8]: True + About the ``inverse_inheritance`` option, here is a concrete example. We assume