Speed up security filter middleware (#108703)

* Speed up security filter middleware

Check the path and query string with the filter expression once instead
of checking the path and query string seperately. If we get a hit than
we check the query string to ensure we give a more verbose error about
where the filter hit.

Additionally since we see the same urls over and over, cache the unquote

* request.url is to expensive, cheaper to join

* aiohttp has a path_qs fast path

* construct the string outselves so it functions exactly as before
This commit is contained in:
J. Nick Koston 2024-01-25 19:17:18 -10:00 committed by GitHub
parent 617e8dd8a5
commit 9de8409f48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable
from functools import lru_cache
import logging
import re
from typing import Final
@ -43,6 +44,7 @@ UNSAFE_URL_BYTES = ["\t", "\r", "\n"]
def setup_security_filter(app: Application) -> None:
"""Create security filter middleware for the app."""
@lru_cache
def _recursive_unquote(value: str) -> str:
"""Handle values that are encoded multiple times."""
if (unquoted := unquote(value)) != value:
@ -54,27 +56,26 @@ def setup_security_filter(app: Application) -> None:
request: Request, handler: Callable[[Request], Awaitable[StreamResponse]]
) -> StreamResponse:
"""Process request and block commonly known exploit attempts."""
for unsafe_byte in UNSAFE_URL_BYTES:
if unsafe_byte in request.path:
_LOGGER.warning(
"Filtered a request with an unsafe byte in path: %s",
request.raw_path,
)
raise HTTPBadRequest
path_with_query_string = f"{request.path}?{request.query_string}"
for unsafe_byte in UNSAFE_URL_BYTES:
if unsafe_byte in path_with_query_string:
if unsafe_byte in request.query_string:
_LOGGER.warning(
"Filtered a request with unsafe byte query string: %s",
request.raw_path,
)
raise HTTPBadRequest
if FILTERS.search(_recursive_unquote(request.path)):
_LOGGER.warning(
"Filtered a potential harmful request to: %s", request.raw_path
"Filtered a request with an unsafe byte in path: %s",
request.raw_path,
)
raise HTTPBadRequest
if FILTERS.search(_recursive_unquote(path_with_query_string)):
# Check the full path with query string first, if its
# a hit, than check just the query string to give a more
# specific warning.
if FILTERS.search(_recursive_unquote(request.query_string)):
_LOGGER.warning(
"Filtered a request with a potential harmful query string: %s",
@ -82,6 +83,11 @@ def setup_security_filter(app: Application) -> None:
)
raise HTTPBadRequest
_LOGGER.warning(
"Filtered a potential harmful request to: %s", request.raw_path
)
raise HTTPBadRequest
return await handler(request)
app.middlewares.append(security_filter_middleware)