From 65988dc14fbe39210cabfd9eda347c2190d4e1c4 Mon Sep 17 00:00:00 2001 From: WSH032 <614337162@qq.com> Date: Tue, 14 Jan 2025 22:05:05 +0800 Subject: [PATCH 1/2] fix(security): add `localhost` rule to `default_proxy_filter` --- src/fastapi_proxy_lib/core/_tool.py | 15 +++++++++++--- tests/test_core_lib.py | 31 +++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/fastapi_proxy_lib/core/_tool.py b/src/fastapi_proxy_lib/core/_tool.py index 8f33b2b..4611e1b 100644 --- a/src/fastapi_proxy_lib/core/_tool.py +++ b/src/fastapi_proxy_lib/core/_tool.py @@ -368,7 +368,12 @@ def check_http_version( def default_proxy_filter(url: httpx.URL) -> Union[None, str]: """Filter by host. - If the host of url is ip address, which is not global ip address, then will reject it. + Reject the following hosts: + + - if the host is ip address, and is not global ip address. e.g: + - `http://127.0.0.1` + - `http://192.168.0.1` + - if the host contains "localhost". Warning: It will consumption time: 3.22~4.7 µs ± 42.6 ns. @@ -381,8 +386,12 @@ def default_proxy_filter(url: httpx.URL) -> Union[None, str]: str: should rejetc the proxy request. The `str` is the reason of reject. """ + host = url.host + if "localhost" in host: + return "Deny proxy for localhost." + try: - ip_address = ipaddress.ip_address(url.host) + ip_address = ipaddress.ip_address(host) except ValueError: return None @@ -401,7 +410,7 @@ def warn_for_none_filter(proxy_filter: None) -> ProxyFilterProto: ... def warn_for_none_filter( - proxy_filter: Union[ProxyFilterProto, None] + proxy_filter: Union[ProxyFilterProto, None], ) -> ProxyFilterProto: """Check whether the argument `proxy_filter` is None. diff --git a/tests/test_core_lib.py b/tests/test_core_lib.py index 5dcae0e..fe74fd9 100644 --- a/tests/test_core_lib.py +++ b/tests/test_core_lib.py @@ -90,10 +90,33 @@ async def _() -> JSONResponse: def test_func_default_proxy_filter() -> None: """Test `fastapi_proxy_lib.core._tool.default_proxy_filter()`.""" - # 禁止访问私有IP - assert default_proxy_filter(httpx.URL("http://www.example.com")) is None - assert default_proxy_filter(httpx.URL("http://1.1.1.1")) is None - assert default_proxy_filter(httpx.URL("http://127.0.0.1")) is not None + # prevent access to private ip + + def _check(url: str, should_pass: bool) -> None: + httpx_url = httpx.URL(url) + if should_pass: + assert default_proxy_filter(httpx_url) is None + else: + assert default_proxy_filter(httpx_url) is not None + + def should_pass(url: str) -> None: + _check(url, True) + + def should_not_pass(url: str) -> None: + _check(url, False) + + # passed + should_pass("http://www.example.com") + should_pass("http://www.example.com/path") + should_pass("http://1.1.1.1") + + # private ip + should_not_pass("http://127.0.0.1") + should_not_pass("http://[::1]") + should_not_pass("http://192.168.0.1") + should_not_pass("http://10.0.0.1") + should_not_pass("http://172.31.0.1") + should_not_pass("http://localhost") def test_non_filter_warning_for_forward_proxy() -> None: From 98c2062b44107ede054bf33ee4adfdb1923a841d Mon Sep 17 00:00:00 2001 From: WSH032 <614337162@qq.com> Date: Tue, 14 Jan 2025 22:10:01 +0800 Subject: [PATCH 2/2] docs(CHANGELOG): update `CHANGELOG.md` --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39230cd..4d63d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#49](https://github.com/WSH032/fastapi-proxy-lib/pull/49) - fix!: bump `httpx-ws >= 0.7.1` to fix frankie567/httpx-ws#29. Thanks [@WSH032](https://github.com/WSH032)! +### Security + +- [#50](https://github.com/WSH032/fastapi-proxy-lib/pull/50) - fix(security): add `localhost` rule to `default_proxy_filter`. Thanks [@WSH032](https://github.com/WSH032)! + ### Internal - [#47](https://github.com/WSH032/fastapi-proxy-lib/pull/47) - test: do not use deprecated and removed APIs of httpx. Thanks [@WSH032](https://github.com/WSH032)!