Security Issue: SSRF Vulnerability in Test LLM API Endpoint
Summary
A Server-Side Request Forgery (SSRF) vulnerability has been identified in the /api/v1/config/test-llm endpoint. This vulnerability allows attackers to probe internal network services by observing different response times and behaviors when accessing open versus closed ports on internal hosts.
Vulnerability Details
Affected Endpoint: /api/v1/config/test-llm
Severity: High
Type: Server-Side Request Forgery (SSRF)
Description
The endpoint accepts user-controlled URLs for LLM API testing without proper validation. When the endpoint attempts to connect to internal network addresses, it exhibits different response characteristics depending on whether the target port is open or closed. This behavior difference can be exploited to:
- Map internal network topology
- Identify running services on internal hosts
- Potentially access sensitive internal services
- Bypass firewall restrictions
Note: Although this is a backend authenticated endpoint, the application contains hardcoded demo credentials that allow anyone to access the admin panel:
- Email:
demo@example.com
- Password:
demo123
These credentials are documented in the deployment guide and FAQ, making it trivial for attackers to authenticate and exploit this SSRF vulnerability without any real access barriers.
Vulnerable Code
File: backend/app/api/v1/endpoints/config.py
@router.post("/test-llm", response_model=LLMTestResponse)
async def test_llm_connection(
request: LLMTestRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""测试LLM连接是否正常"""
# ... code ...
# Issue: baseUrl accepts user input without validation
base_url = request.baseUrl or DEFAULT_BASE_URLS.get(provider, "")
# ... code ...
# Creates config with user-controlled URL
config = LLMConfig(
provider=provider,
api_key=request.apiKey,
model=model,
base_url=request.baseUrl, # ⚠️ No validation for internal IPs
timeout=test_timeout,
temperature=test_temperature,
max_tokens=test_max_tokens,
)
# Adapter makes request to user-controlled URL
adapter = LiteLLMAdapter(config)
response = await adapter.complete(test_request)
The request.baseUrl parameter is directly used without checking if it points to internal network addresses, allowing SSRF attacks.
Proof of Concept
Request Packet
POST /api/v1/config/test-llm HTTP/1.1
Host: localhost:3000
sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"
Origin: http://localhost:3000
Sec-Fetch-Dest: empty
sec-ch-ua-platform: "Windows"
Cookie: ECS[visit_times]=4
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/admin
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Njk1NjE2MjYsInN1YiI6ImQ4NGQ3YTUwLTZjZDEtNDQ3MC05YWQwLWZlZmM0Zjg2MDJkYSJ9.isM5DVTUWQwf4xLEqyNEURYwEqo3g_lrPqDYzCuSId8
sec-ch-ua-mobile: ?0
Accept: application/json, text/plain, */*
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Content-Length: 157
{"provider":"openai","apiKey":"sk-Qx9F2aL7MZpR3K4HnC8WJd0VtUeS1YB6GmE5rAqPSoLkX","model":"gemini-3-flash-preview","baseUrl":"http://127.0.0.1:8001"}
Response Time Comparison
Case 1: Internal service exists (port open)
Case 2: Internal service does not exist (port closed)
Observable Differences
- Response Time: Significant difference in response latency between open and closed ports
- Status Codes: Different HTTP status codes returned
- Error Messages: Varying error messages that leak internal network information
Impact
This vulnerability can be exploited to:
- Scan internal network infrastructure
- Identify internal services and their availability
- Potentially access or interact with internal APIs, databases, or services
- Bypass network segmentation and access controls
- Perform denial of service attacks against internal services
Exploitation Barrier: Low - The hardcoded demo credentials (demo@example.com / demo123) effectively make this an unauthenticated SSRF vulnerability, as any attacker can easily obtain valid authentication tokens.
Affected Versions
- All versions prior to fix
Recommended Fix
Add URL validation to block internal network addresses before making requests.
Implementation:
import ipaddress
import socket
from urllib.parse import urlparse
INTERNAL_IP_RANGES = [
ipaddress.ip_network('127.0.0.0/8'), # Loopback
ipaddress.ip_network('10.0.0.0/8'), # Private Class A
ipaddress.ip_network('172.16.0.0/12'), # Private Class B
ipaddress.ip_network('192.168.0.0/16'), # Private Class C
ipaddress.ip_network('169.254.0.0/16'), # Link-local
]
def is_safe_url(url: str) -> bool:
"""Validate URL to prevent SSRF attacks"""
if not url:
return True
parsed = urlparse(url)
hostname = parsed.hostname
if not hostname:
return True
# Block localhost
if hostname in ['localhost', '127.0.0.1', '0.0.0.0', '::1']:
return False
# Resolve and check IP address
try:
ip = ipaddress.ip_address(hostname)
# Direct IP check
return not any(ip in network for network in INTERNAL_IP_RANGES)
except ValueError:
# It's a hostname, resolve it
try:
resolved_ip = socket.gethostbyname(hostname)
ip = ipaddress.ip_address(resolved_ip)
return not any(ip in network for network in INTERNAL_IP_RANGES)
except (socket.gaierror, ValueError):
return False
# Add validation in the endpoint:
@router.post("/test-llm", response_model=LLMTestResponse)
async def test_llm_connection(request: LLMTestRequest, ...):
# Validate baseUrl before using it
if request.baseUrl and not is_safe_url(request.baseUrl):
raise HTTPException(
status_code=400,
detail="Access to internal network addresses is not allowed"
)
# ... rest of the code ...
Reporter: flashzyc
Date: January 20, 2026
Security Issue: SSRF Vulnerability in Test LLM API Endpoint
Summary
A Server-Side Request Forgery (SSRF) vulnerability has been identified in the
/api/v1/config/test-llmendpoint. This vulnerability allows attackers to probe internal network services by observing different response times and behaviors when accessing open versus closed ports on internal hosts.Vulnerability Details
Affected Endpoint:
/api/v1/config/test-llmSeverity: High
Type: Server-Side Request Forgery (SSRF)
Description
The endpoint accepts user-controlled URLs for LLM API testing without proper validation. When the endpoint attempts to connect to internal network addresses, it exhibits different response characteristics depending on whether the target port is open or closed. This behavior difference can be exploited to:
Note: Although this is a backend authenticated endpoint, the application contains hardcoded demo credentials that allow anyone to access the admin panel:
demo@example.comdemo123These credentials are documented in the deployment guide and FAQ, making it trivial for attackers to authenticate and exploit this SSRF vulnerability without any real access barriers.
Vulnerable Code
File:
backend/app/api/v1/endpoints/config.pyThe
request.baseUrlparameter is directly used without checking if it points to internal network addresses, allowing SSRF attacks.Proof of Concept
Request Packet
Response Time Comparison
Case 1: Internal service exists (port open)
Case 2: Internal service does not exist (port closed)
Observable Differences
Impact
This vulnerability can be exploited to:
Exploitation Barrier: Low - The hardcoded demo credentials (
demo@example.com/demo123) effectively make this an unauthenticated SSRF vulnerability, as any attacker can easily obtain valid authentication tokens.Affected Versions
Recommended Fix
Add URL validation to block internal network addresses before making requests.
Implementation:
Reporter: flashzyc
Date: January 20, 2026