mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
fix: sign media server image proxy URLs
This commit is contained in:
@@ -37,7 +37,12 @@ _stub_module("app.chain.media", MediaChain=_Dummy)
|
||||
_stub_module("app.chain.mediaserver", MediaServerChain=_Dummy)
|
||||
_stub_module("app.chain.search", SearchChain=_Dummy)
|
||||
_stub_module("app.chain.system", SystemChain=_Dummy)
|
||||
_stub_module("app.core.event", eventmanager=_Dummy(), Event=_Dummy)
|
||||
_stub_module(
|
||||
"app.core.event",
|
||||
eventmanager=_Dummy(),
|
||||
Event=_Dummy,
|
||||
EventManager=_Dummy,
|
||||
)
|
||||
_stub_module("app.core.metainfo", MetaInfo=_Dummy)
|
||||
_stub_module("app.core.module", ModuleManager=_Dummy)
|
||||
_stub_module(
|
||||
@@ -82,10 +87,12 @@ from app.api.endpoints import system as system_endpoint
|
||||
|
||||
|
||||
class NettestSecurityTest(unittest.TestCase):
|
||||
def test_fetch_image_allows_private_media_server_image_path(self):
|
||||
def test_fetch_image_allows_signed_private_url(self):
|
||||
"""
|
||||
已配置媒体服务器的内网图片接口需要继续允许代理,保证前端封面显示。
|
||||
服务端签名过的私网图片 URL 可以继续代理,保证前端封面显示。
|
||||
"""
|
||||
image_url = "http://192.168.1.50:8096/System/Info/Public"
|
||||
signed_url = system_endpoint.SecurityUtils.sign_url(image_url)
|
||||
image_helper = Mock()
|
||||
image_helper.async_fetch_image = AsyncMock(return_value=b"image-bytes")
|
||||
|
||||
@@ -96,14 +103,18 @@ class NettestSecurityTest(unittest.TestCase):
|
||||
):
|
||||
resp = asyncio.run(
|
||||
system_endpoint.fetch_image(
|
||||
url="http://192.168.1.50:8096/Items/abc/Images/Primary",
|
||||
allowed_domains={"http://192.168.1.50:8096"},
|
||||
media_server_domains={"http://192.168.1.50:8096"},
|
||||
url=signed_url,
|
||||
allowed_domains=set(),
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
image_helper.async_fetch_image.assert_awaited_once()
|
||||
image_helper.async_fetch_image.assert_awaited_once_with(
|
||||
url=image_url,
|
||||
proxy=None,
|
||||
use_cache=False,
|
||||
cookies=None,
|
||||
)
|
||||
|
||||
def test_fetch_image_blocks_private_allowed_url_before_request(self):
|
||||
"""
|
||||
@@ -123,39 +134,23 @@ class NettestSecurityTest(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(resp)
|
||||
|
||||
def test_fetch_image_blocks_private_media_server_non_image_path(self):
|
||||
def test_fetch_image_blocks_tampered_signed_private_url(self):
|
||||
"""
|
||||
媒体服务器 host 只放行图片接口,不放行同 host 下的任意 API。
|
||||
私网签名绑定完整 URL,改动路径后不能继续代理。
|
||||
"""
|
||||
signed_url = system_endpoint.SecurityUtils.sign_url(
|
||||
"http://192.168.1.50:8096/Items/abc/Images/Primary"
|
||||
).replace("/Items/abc/Images/Primary", "/System/Info/Public")
|
||||
|
||||
class FailIfCalled:
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise AssertionError("fetch_image should block non-image media server paths")
|
||||
raise AssertionError("fetch_image should block tampered signed URLs")
|
||||
|
||||
with patch.object(system_endpoint, "ImageHelper", FailIfCalled):
|
||||
resp = asyncio.run(
|
||||
system_endpoint.fetch_image(
|
||||
url="http://192.168.1.50:8096/System/Info/Public",
|
||||
allowed_domains={"http://192.168.1.50:8096"},
|
||||
media_server_domains={"http://192.168.1.50:8096"},
|
||||
)
|
||||
)
|
||||
|
||||
self.assertIsNone(resp)
|
||||
|
||||
def test_fetch_image_blocks_traversal_in_media_server_image_path(self):
|
||||
"""
|
||||
编码后的路径穿越不能借媒体图片前缀绕过私网 SSRF 防护。
|
||||
"""
|
||||
class FailIfCalled:
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise AssertionError("fetch_image should block traversal image paths")
|
||||
|
||||
with patch.object(system_endpoint, "ImageHelper", FailIfCalled):
|
||||
resp = asyncio.run(
|
||||
system_endpoint.fetch_image(
|
||||
url="http://192.168.1.50:5666/api/v1/sys/img/%2e%2e/manager/user/list",
|
||||
allowed_domains={"http://192.168.1.50:5666"},
|
||||
media_server_domains={"http://192.168.1.50:5666"},
|
||||
url=signed_url,
|
||||
allowed_domains=set(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user