| Field | Value |
|---|---|
| Product | ApiFlow |
| Vendor | trueleaf.cn |
| Vulnerability Type | Server-Side Request Forgery (CWE-918) |
| Affected Versions | <= 0.9.81 (all versions) |
| Severity | High (CVSS 8.6) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | None |
| User Interaction | None |
| Scope | Unchanged |
| Confidentiality Impact | High |
| Integrity Impact | None |
| Availability Impact | None |
ApiFlow is an API documentation management platform written in TypeScript/Node.js. The HTTP proxy functionality in the server component contains a Server-Side Request Forgery (SSRF) vulnerability that allows unauthenticated attackers to bypass URL validation and make arbitrary requests to internal network resources.
The vulnerability exists in the HttpProxyService class where URL validation is performed only on the initial request URL, but not on redirect targets. By using an external redirect service, attackers can bypass the IP address allowlist and access internal services such as databases, cloud metadata endpoints, and other sensitive internal resources.
| Component | File Path |
|---|---|
| HTTP Proxy Service | packages/server/src/service/proxy/http_proxy.service.ts |
| HTTP Proxy Controller | packages/server/src/controller/proxy/http_proxy.controller.ts |
| API Endpoint | POST /api/proxy/http |
File: packages/server/src/service/proxy/http_proxy.service.ts
URL Validation Function (Lines 17-63):
private validateUrlSecurity(url: string): { valid: boolean; error?: string } {
try {
const parsedUrl = new URL(url);
// Only allow http and https protocols
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return {
valid: false,
error: `Unsupported protocol:${parsedUrl.protocol}`
};
}
const hostname = parsedUrl.hostname.toLowerCase();
// Forbidden internal IP patterns
const forbiddenPatterns = [
/^127\\./, // 127.0.0.0/8
/^10\\./, // 10.0.0.0/8
/^172\\.(1[6-9]|2\\d|3[01])\\./, // 172.16.0.0/12
/^192\\.168\\./, // 192.168.0.0/16
/^169\\.254\\./, // Link-local
/^0\\./, // 0.0.0.0/8
/^localhost$/, // localhost
/^::1$/, // IPv6 loopback
/^fe80:/, // IPv6 link-local
/^fc00:/, // IPv6 unique local
/^fd00:/, // IPv6 unique local
];
for (const pattern of forbiddenPatterns) {
if (pattern.test(hostname)) {
return {
valid: false,
error: `Access to internal address is forbidden:${hostname}`
};
}
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: `Invalid URL format:${(error as Error).message}`
};
}
}
Request Execution with Redirect Following (Lines 140-146):
const gotOptions: Omit<OptionsInit, 'isStream'> = {
url: params.url,
method: params.method,
body: willSendBody,
headers,
followRedirect: params.followRedirect ?? true, // VULNERABILITY: Redirects followed by default
maxRedirects: params.maxRedirects ?? 10, // Up to 10 redirects allowed
// ...
};
The vulnerability stems from a fundamental design flaw in the URL validation logic:
validateUrlSecurity) is performed only once before the request is initiated.