mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 02:28:30 +08:00
fix: SSRF bypass via raw httpx.get in API tool schema fetch (#37746)
Co-authored-by: 归青 <guiqing.lzw@antgroup.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
This commit is contained in:
parent
f09d2b191f
commit
ae0d6ee214
@ -5,10 +5,10 @@ from json import loads as json_loads
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Any, TypedDict
|
||||
|
||||
import httpx
|
||||
from flask import has_request_context, request
|
||||
from yaml import YAMLError, safe_load
|
||||
|
||||
from core.helper import ssrf_proxy
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
from core.tools.entities.tool_bundle import ApiToolBundle
|
||||
from core.tools.entities.tool_entities import ApiProviderSchemaType, ToolParameter
|
||||
@ -376,7 +376,7 @@ class ApiBasedToolSchemaParser:
|
||||
raise ToolNotSupportedError("Only openapi is supported now.")
|
||||
|
||||
# get openapi yaml
|
||||
response = httpx.get(
|
||||
response = ssrf_proxy.get(
|
||||
api_url, headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "}, timeout=5
|
||||
)
|
||||
|
||||
|
||||
@ -2,12 +2,12 @@ import json
|
||||
import logging
|
||||
from typing import Any, Literal, TypedDict, cast
|
||||
|
||||
from httpx import get
|
||||
from pydantic import TypeAdapter
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from core.entities.provider_entities import ProviderConfig, ProviderConfigType
|
||||
from core.helper import ssrf_proxy
|
||||
from core.tools.__base.tool_runtime import ToolRuntime
|
||||
from core.tools.custom_tool.provider import ApiToolProviderController
|
||||
from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
|
||||
@ -231,7 +231,7 @@ class ApiToolManageService:
|
||||
}
|
||||
|
||||
try:
|
||||
response = get(url, headers=headers, timeout=10)
|
||||
response = ssrf_proxy.get(url, headers=headers, timeout=10)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Got status code {response.status_code}")
|
||||
schema = response.text
|
||||
|
||||
@ -327,7 +327,7 @@ def test_parse_openai_plugin_json_branches(app):
|
||||
def test_parse_openai_plugin_json_http_branches(app):
|
||||
with app.test_request_context():
|
||||
response = type("Resp", (), {"status_code": 500, "text": "", "close": Mock()})()
|
||||
with patch("core.tools.utils.parser.httpx.get", return_value=response):
|
||||
with patch("core.tools.utils.parser.ssrf_proxy.get", return_value=response):
|
||||
with pytest.raises(ToolProviderNotFoundError, match="cannot get openapi yaml"):
|
||||
ApiBasedToolSchemaParser.parse_openai_plugin_json_to_tool_bundle(
|
||||
'{"api": {"url": "https://x", "type": "openapi"}}'
|
||||
@ -335,7 +335,7 @@ def test_parse_openai_plugin_json_http_branches(app):
|
||||
response.close.assert_called_once()
|
||||
|
||||
success_response = type("Resp", (), {"status_code": 200, "text": "openapi: 3.0.0", "close": Mock()})()
|
||||
with patch("core.tools.utils.parser.httpx.get", return_value=success_response):
|
||||
with patch("core.tools.utils.parser.ssrf_proxy.get", return_value=success_response):
|
||||
with patch(
|
||||
"core.tools.utils.parser.ApiBasedToolSchemaParser.parse_openapi_yaml_to_tool_bundle",
|
||||
return_value=["bundle"],
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
from unittest.mock import Mock
|
||||
|
||||
from services.tools.api_tools_manage_service import ApiToolManageService
|
||||
|
||||
|
||||
def test_get_api_tool_provider_remote_schema_uses_ssrf_proxy_get(monkeypatch) -> None:
|
||||
schema = """
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Demo API", "version": "1.0.0"},
|
||||
"servers": [{"url": "https://api.example.com"}],
|
||||
"paths": {}
|
||||
}
|
||||
"""
|
||||
url = "https://example.com/openapi.json"
|
||||
expected_headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko)"
|
||||
" Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
|
||||
"Accept": "*/*",
|
||||
}
|
||||
mock_get = Mock(return_value=Mock(status_code=200, text=schema))
|
||||
mock_parser = Mock()
|
||||
|
||||
monkeypatch.setattr("services.tools.api_tools_manage_service.ssrf_proxy.get", mock_get)
|
||||
monkeypatch.setattr(ApiToolManageService, "parser_api_schema", mock_parser)
|
||||
|
||||
result = ApiToolManageService.get_api_tool_provider_remote_schema("user-1", "tenant-1", url)
|
||||
|
||||
assert result == {"schema": schema}
|
||||
mock_get.assert_called_once_with(url, headers=expected_headers, timeout=10)
|
||||
mock_parser.assert_called_once_with(schema)
|
||||
Loading…
Reference in New Issue
Block a user