From 099c3c659dc91eb852b289ca6310e6ce85eb7198 Mon Sep 17 00:00:00 2001 From: Christoph Volkert Date: Fri, 3 Nov 2023 23:38:37 +0100 Subject: [PATCH] Reject NUL byte in URLs Sentry reported f.e. for the URL `http://ubuntuusers.de/calendar/2014/03/11/ubuntu-developer-summit-14-1%00%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%BF%BD%2527%2522/ics/` the traceback ``` ValueError: A string literal cannot contain NUL (0x00) characters. File "django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "django/core/handlers/base.py", line 181, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "inyoka/portal/views.py", line 1464, in calendar_ical event = Event.objects.get(slug=slug) File "django/db/models/manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "django/db/models/query.py", line 431, in get num = len(clone) File "django/db/models/query.py", line 262, in __len__ self._fetch_all() File "django/db/models/query.py", line 1324, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "django/db/models/query.py", line 51, in __iter__ results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size) File "django/db/models/sql/compiler.py", line 1175, in execute_sql cursor.execute(sql, params) File "django/db/backends/utils.py", line 66, in execute return self._execute_with_wrappers(sql, params, many=False, executor=self._execute) File "django/db/backends/utils.py", line 75, in _execute_with_wrappers return executor(sql, params, many, context) File "django/db/backends/utils.py", line 84, in _execute return self.cursor.execute(sql, params) ``` --- inyoka/middlewares/common.py | 9 ++++++++- tests/utils/test_middleware.py | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_middleware.py diff --git a/inyoka/middlewares/common.py b/inyoka/middlewares/common.py index a10d9823c..4e183b209 100644 --- a/inyoka/middlewares/common.py +++ b/inyoka/middlewares/common.py @@ -16,6 +16,7 @@ """ from django.conf import settings from django.contrib import messages +from django.http import HttpResponse from django.middleware.common import CommonMiddleware from django_hosts.middleware import HostsRequestMiddleware @@ -35,7 +36,13 @@ def process_request(self, request): request.watch = StopWatch() request.watch.start() - # IMPORTANT: Since we run some setupcode (mainly locals), this middleware + # reject URLs with NUL byte. + # If those are passed to the ORM with postgres, an exception is raised + # It's also mentioned in https://datatracker.ietf.org/doc/html/rfc3986#section-7.3 + if '\x00' in request.path: + return HttpResponse(content='The URL contained NUL characters', status=400, content_type='text/plain') + + # IMPORTANT: Since we run some setup-code (mainly locals), this middleware # needs to be the first one, hence we manually dispatch to HostsMiddleware response = HostsRequestMiddleware.process_request(self, request) if response is not None: diff --git a/tests/utils/test_middleware.py b/tests/utils/test_middleware.py new file mode 100644 index 000000000..7b24c515d --- /dev/null +++ b/tests/utils/test_middleware.py @@ -0,0 +1,27 @@ +""" + tests.apps.utils.test_middleware + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test Inyoka-custom middlewares. + + :copyright: (c) 2012-2023 by the Inyoka Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" +from django.conf import settings + +from inyoka.utils.test import InyokaClient, TestCase + + +class TestNULByte(TestCase): + + client_class = InyokaClient + + def test_get_null(self): + url = f'http://{ settings.BASE_DOMAIN_NAME }/1%00%EF/' + response = self.client.get(url) + self.assertEqual(response.status_code, 400) + + def test_get_null_calender(self): + url = f'http://{ settings.BASE_DOMAIN_NAME }/2023/11/18/lpd-2023-10\x00EF2522/ics/' + response = self.client.get(url) + self.assertEqual(response.status_code, 400)