diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e649cf..c901d1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#46](https://github.com/WSH032/fastapi-proxy-lib/pull/46) - fix: don't use module-level logging methods. Thanks [@dvarrazzo](https://github.com/dvarrazzo) - [#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)! + ### Removed - [#49](https://github.com/WSH032/fastapi-proxy-lib/pull/49) - Drop support for `Python 3.8`. diff --git a/src/fastapi_proxy_lib/core/_tool.py b/src/fastapi_proxy_lib/core/_tool.py index 6f23b2f..685fc25 100644 --- a/src/fastapi_proxy_lib/core/_tool.py +++ b/src/fastapi_proxy_lib/core/_tool.py @@ -370,7 +370,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. @@ -383,8 +388,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 @@ -403,7 +412,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: