| Title | kekingcn kkFileView 4.4.0/5.0.x Server-Side Request Forgery |
|---|
| Description | Defense Mechanism (trust.host whitelist):
kkFileView has historically suffered from multiple SSRF (Server-Side Request Forgery) and arbitrary file read vulnerabilities. To remediate these issues, the official developers introduced a strict trust.host (trusted site verification) configuration in subsequent versions. When the component receives a file retrieval request, it first parses the Host of the target URL. However, because the system's security filter for /getCorsFile validates the url parameter, while the controller actually reads the urlPath parameter, attackers can use a trusted URL to bypass the host whitelist. This allows them to trick the server into requesting an internal address and returning the response.
Exploit Payload:
HTTP
GET /getCorsFile?url=[Base64 encoded then URL encoded (trusted address)]&urlPath=[Base64 encoded then URL encoded (internal address)] HTTP/2
Host: <cyber_range_domain>
Connection: close
Source code address:https://github.com/kekingcn/kkFileView
Vulnerability source code location
Core Controller: server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java:147
@GetMapping("/getCorsFile")
public void getCorsFile(@RequestParam String urlPath, // ← Controller only takes urlPath
@RequestParam(required = false) String key, ...) {
...
urlPath = WebUtils.decodeUrl(urlPath, encryption); // Decode urlPath
url = WebUtils.normalizedURL(urlPath); // Construct URL using urlPath
...
// Only perform protocol whitelist validation, no host validation
if (! isHttpUrl(url) && ! isFtpUrl(url)) { ... return; }
...
HttpRequestUtils.executeHttpRequest(url, httpClient, ...) ; // Directly initiate a server request
}
Trust Host Filter: server/src/main/java/cn/keking/web/filter/TrustHostFilter.java:50
public void doFilter(...) {
String url = WebUtils.getSourceUrl(request); // ← Which parameter does the filter take?
String host = WebUtils.getHost(url);
if (isNotTrustHost(host) || ! WebUtils.isValidUrl(url)) {
// Intercept
} else {
chain.doFilter(request, response); // Allow passage
}
}
Critical defect point: server/src/main/java/cn/keking/utils/WebUtils.java:243 (getSourceUrl)
public static String getSourceUrl(ServletRequest request) {
String url = request.getParameter("url"); // ① First, check the URL
String urls = request.getParameter("urls");
String currentUrl = request.getParameter("currentUrl");
String urlPath = request.getParameter("urlPath"); // ② urlPath is placed at the end
...
if (StringUtils.isNotBlank(url)) return decodeUrl(url, encryption); // Prefer to return url
if (StringUtils.isNotBlank(currentUrl)) return decodeUrl(currentUrl, encryption);
if (StringUtils.isNotBlank(urlPath)) return decodeUrl(urlPath, encryption); // This code path is never reached when the URL is non-empty
...
}
Cause of vulnerability: Validation parameter ≠ Usage parameter (parameter confusion)
TrustHostFilter is registered to /getCorsFile in WebConfig.java:48, and it calls getSourceUrl() to retrieve the URL to be validated. However, getSourceUrl() returns the first non-empty parameter based on the priority order: url → currentUrl → urlPath → urls. Meanwhile, the getCorsFile controller binds only urlPath via @RequestParam String urlPath.
The two are reading entirely different parameters. Thus, the attack payload:
GET /getCorsFile? url=<base64+urlenc(trusted external address)>&urlPath=<base64+urlenc(internal address)>&key=xxx
- Filter side: If url is non-empty → getSourceUrl returns url (trusted address) → isNotTrustHost passes, isValidUrl passes → allowed
- Controller side: Spring injects urlPath (internal address) into the method → after decoding, HttpRequestUtils.executeHttpRequest directly requests the internal network; response is returned unchanged to the attacker → SSRF triggered
This fully matches your described dual-parameter structure: "url=trusted address, urlPath=internal address."
Secondary vulnerabilities (lack of layered defense):
1. OnlinePreviewController.java:184 — The controller performs only protocol checks (isHttpUrl/isFtpUrl) on the decoded urlPath, with no host or network segment validation. Even if the filter is bypassed, there is no secondary defense.
2. TrustHostFilter includes urlPath—a parameter used for actual fetching—into the candidate set for getSourceUrl, but assigns it lower priority than url, making this parameter a shadow parameter that can be "overridden." This represents an ambiguity in parameter selection at the design level.
Recommended fixes (for reference):
Have both the filter and controller validate the same parameter. The most direct approach: In TrustHostFilter.doFilter, explicitly include urlPath for host validation when handling /getCorsFile; alternatively, within the getCorsFile controller, perform an additional isNotTrustHost(host) whitelist check and protocol validation on the decoded urlPath, rather than relying solely on the preceding filter. Additionally, recommend tightening the multi-parameter priority logic in getSourceUrl to eliminate the risk of "passing validation via one parameter while exploiting SSRF via another."
|
|---|
| User | BlackSpdier (UID 89912) |
|---|
| Submission | 09/26/2026 05:05 (1 month ago) |
|---|
| Moderation | 10/25/2026 08:18 (29 days later) |
|---|
| Status | Duplicate |
|---|
| VulDB entry | 406094 [sg-summer kkFileView 4.2.0 Cross-Origin File Proxy Endpoint urlPath server-side request forgery] |
|---|
| Points | 0 |
|---|