From 925712e3ecd9fbc1776e3b4d187dc521589be0eb Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 21 Mar 2017 10:16:49 -0400 Subject: [PATCH] block users from making looping filters which can DoS Tower --- awx/api/filters.py | 7 +++++++ awx/main/tests/unit/api/test_filters.py | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/awx/api/filters.py b/awx/api/filters.py index 87097dc308..b2163a3342 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -92,6 +92,9 @@ class FieldLookupBackend(BaseFilterBackend): # sure user cannot query using objects he could not view. new_parts = [] + # Store of all the fields used to detect repeats + field_set = set([]) + for name in parts[:-1]: # HACK: Make project and inventory source filtering by old field names work for backwards compatibility. if model._meta.object_name in ('Project', 'InventorySource'): @@ -124,6 +127,10 @@ class FieldLookupBackend(BaseFilterBackend): raise PermissionDenied(_('Filtering on %s is not allowed.' % name)) elif getattr(field, '__prevent_search__', False): raise PermissionDenied(_('Filtering on %s is not allowed.' % name)) + if field in field_set: + # Field traversed twice, could create infinite JOINs, DoSing Tower + raise ParseError(_('Loops not allowed in filters, detected on field {}.').format(field.name)) + field_set.add(field) model = getattr(field, 'related_model', None) or field.model if parts: diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py index 45eec0df1f..1625c946b1 100644 --- a/awx/main/tests/unit/api/test_filters.py +++ b/awx/main/tests/unit/api/test_filters.py @@ -2,7 +2,7 @@ import pytest -from rest_framework.exceptions import PermissionDenied +from rest_framework.exceptions import PermissionDenied, ParseError from awx.api.filters import FieldLookupBackend from awx.main.models import (AdHocCommand, AuthToken, CustomInventoryScript, Credential, Job, JobTemplate, SystemJob, @@ -77,3 +77,10 @@ def test_filter_sensitive_fields_and_relations(model, query): with pytest.raises(PermissionDenied) as excinfo: field, new_lookup = field_lookup.get_field_from_lookup(model, query) assert 'not allowed' in str(excinfo.value) + + +def test_looping_filters_prohibited(): + field_lookup = FieldLookupBackend() + with pytest.raises(ParseError) as loop_exc: + field_lookup.get_field_from_lookup(Job, 'job_events__job__job_events') + assert 'job_events' in str(loop_exc.value)