diff --git a/homeassistant/components/http/security_filter.py b/homeassistant/components/http/security_filter.py index 57ae9063170..a9b32bd7f4c 100644 --- a/homeassistant/components/http/security_filter.py +++ b/homeassistant/components/http/security_filter.py @@ -5,6 +5,7 @@ from collections.abc import Awaitable, Callable import logging import re from typing import Final +from urllib.parse import unquote from aiohttp.web import Application, HTTPBadRequest, Request, StreamResponse, middleware @@ -39,18 +40,24 @@ FILTERS: Final = re.compile( def setup_security_filter(app: Application) -> None: """Create security filter middleware for the app.""" + def _recursive_unquote(value: str) -> str: + """Handle values that are encoded multiple times.""" + if (unquoted := unquote(value)) != value: + unquoted = _recursive_unquote(unquoted) + return unquoted + @middleware async def security_filter_middleware( request: Request, handler: Callable[[Request], Awaitable[StreamResponse]] ) -> StreamResponse: - """Process request and tblock commonly known exploit attempts.""" - if FILTERS.search(request.path): + """Process request and block commonly known exploit attempts.""" + if FILTERS.search(_recursive_unquote(request.path)): _LOGGER.warning( "Filtered a potential harmful request to: %s", request.raw_path ) raise HTTPBadRequest - if FILTERS.search(request.query_string): + if FILTERS.search(_recursive_unquote(request.query_string)): _LOGGER.warning( "Filtered a request with a potential harmful query string: %s", request.raw_path, diff --git a/tests/components/http/test_security_filter.py b/tests/components/http/test_security_filter.py index 82e8382461b..1c139a59161 100644 --- a/tests/components/http/test_security_filter.py +++ b/tests/components/http/test_security_filter.py @@ -49,7 +49,17 @@ async def test_ok_requests( ("/", {"test": "test/../../api"}, True), ("/", {"test": "/test/%2E%2E%2f%2E%2E%2fapi"}, True), ("/", {"test": "test/%2E%2E%2f%2E%2E%2fapi"}, True), + ("/", {"test": "test/%252E%252E/api"}, True), + ("/", {"test": "test/%252E%252E%2fapi"}, True), + ( + "/", + {"test": "test/%2525252E%2525252E%2525252f%2525252E%2525252E%2525252fapi"}, + True, + ), + ("/test/.%252E/api", {}, False), + ("/test/%252E%252E/api", {}, False), ("/test/%2E%2E%2f%2E%2E%2fapi", {}, False), + ("/test/%2525252E%2525252E%2525252f%2525252E%2525252E/api", {}, False), ("/", {"sql": ";UNION SELECT (a, b"}, True), ("/", {"sql": "UNION%20SELECT%20%28a%2C%20b"}, True), ("/UNION%20SELECT%20%28a%2C%20b", {}, False), @@ -87,7 +97,7 @@ async def test_bad_requests( None, http.request, "GET", - f"http://{mock_api_client.host}:{mock_api_client.port}/{request_path}{man_params}", + f"http://{mock_api_client.host}:{mock_api_client.port}{request_path}{man_params}", request_params, )