Summary
The htmlKeywordsFromUrl function in the FetchController class accepts user-provided URLs and makes HTTP requests to them without validating that the destination is not an internal or private network resource. This Server-Side Request Forgery (SSRF) vulnerability allows authenticated attackers to use the application server to perform port scanning and service discovery on internal networks. However, the practical impact is very limited because the function only extracts content from HTML meta keywords tags, which prevents meaningful data exfiltration from databases, APIs, or cloud metadata endpoints.
Details
The htmlKeywordsFromUrl function in the FetchController class accepts user-provided URLs and makes HTTP requests to them without validating that the destination is not an internal or private network resource. This Server-Side Request Forgery (SSRF) vulnerability allows authenticated attackers to use the application server to perform port scanning and service discovery on internal networks. However, the practical impact is very limited because the function only extracts content from HTML meta keywords tags, which prevents meaningful data exfiltration from databases, APIs, or cloud metadata endpoints.
Vulnerable Endpoints
POST /fetch/keywords-for-url - Accepts user-provided URLs and makes server-side HTTP requests without validation.
The vulnerable code is located in app/Http/Controllers/FetchController.php:
// Lines 95-121
public function htmlKeywordsFromUrl(Request $request)
{
$request->validate([
'url' => ['url'], // Only validates URL format, not destination
]);
$url = $request->input('url');
$newRequest = setupHttpRequest(3);
$response = $newRequest->get($url); // Makes request to user-provided URL
if ($response->successful()) {
$html5 = new HTML5();
$dom = $html5->loadHTML($response->body());
$keywords = [];
foreach ($dom->getElementsByTagName('meta') as $metaTag) {
if (strtolower($metaTag->getAttribute('name')) === 'keywords') {
$keywords = explode(',', $metaTag->getAttributeNode('content')?->value);
$keywords = array_map(fn($keyword) => trim(e($keyword)), $keywords);
array_push($keywords, ...$keywords);
}
}
return response()->json(['keywords' => $keywords]);
}
return response()->json(['keywords' => null]);
}
The function only validates that the input is a properly formatted URL using Laravel's built-in url validation rule, but does not verify the destination IP address. This allows attackers to target localhost (127.0.0.1), private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local addresses (169.254.0.0/16), and other reserved IP ranges. The impact is limited to network reconnaissance as the response parsing only extracts HTML meta keywords, which are not present in database protocols, modern APIs, or cloud metadata endpoints.
PoC
Steps to Reproduce
- Log in to LinkAce as any authenticated user
- Open the browser developer console
- Send a POST request to
/fetch/keywords-for-url with a JSON payload containing an external URL: {"url": "https://www.w3schools.com"}. Include the CSRF token from the page metadata
- Observe that the server makes the HTTP request and returns keywords from the target page, confirming the SSRF vulnerability
- Test internal network access by sending a request with an internal target:
{"url": "http://127.0.0.1:80"}
- Observe that the server attempts to connect to localhost, demonstrating the ability to probe internal network resources
- Repeat with different ports and IP addresses to perform port scanning and service discovery on the internal network
- Note that data exfiltration is limited to HTML pages containing meta keywords tags, which excludes most modern services and APIs
Proof of Concept (Media)

Recommendations
Implement IP address validation before making HTTP requests to user-provided URLs. After validating the URL format, extract the hostname using parse_url($url, PHP_URL_HOST), resolve it to an IP address using gethostbyname(), and validate that the IP is not in a private or reserved range using filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE). This will block requests to localhost (127.0.0.1), private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and link-local addresses (169.254.0.0/16) while still allowing legitimate external URLs. Consider implementing a whitelist of allowed domains if the functionality only needs to support specific external services.
Summary
The
htmlKeywordsFromUrlfunction in the FetchController class accepts user-provided URLs and makes HTTP requests to them without validating that the destination is not an internal or private network resource. This Server-Side Request Forgery (SSRF) vulnerability allows authenticated attackers to use the application server to perform port scanning and service discovery on internal networks. However, the practical impact is very limited because the function only extracts content from HTML meta keywords tags, which prevents meaningful data exfiltration from databases, APIs, or cloud metadata endpoints.Details
The
htmlKeywordsFromUrlfunction in the FetchController class accepts user-provided URLs and makes HTTP requests to them without validating that the destination is not an internal or private network resource. This Server-Side Request Forgery (SSRF) vulnerability allows authenticated attackers to use the application server to perform port scanning and service discovery on internal networks. However, the practical impact is very limited because the function only extracts content from HTML meta keywords tags, which prevents meaningful data exfiltration from databases, APIs, or cloud metadata endpoints.Vulnerable Endpoints
POST /fetch/keywords-for-url- Accepts user-provided URLs and makes server-side HTTP requests without validation.The vulnerable code is located in
app/Http/Controllers/FetchController.php:The function only validates that the input is a properly formatted URL using Laravel's built-in
urlvalidation rule, but does not verify the destination IP address. This allows attackers to target localhost (127.0.0.1), private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local addresses (169.254.0.0/16), and other reserved IP ranges. The impact is limited to network reconnaissance as the response parsing only extracts HTML meta keywords, which are not present in database protocols, modern APIs, or cloud metadata endpoints.PoC
Steps to Reproduce
/fetch/keywords-for-urlwith a JSON payload containing an external URL:{"url": "https://www.w3schools.com"}. Include the CSRF token from the page metadata{"url": "http://127.0.0.1:80"}Proof of Concept (Media)
Recommendations
Implement IP address validation before making HTTP requests to user-provided URLs. After validating the URL format, extract the hostname using
parse_url($url, PHP_URL_HOST), resolve it to an IP address usinggethostbyname(), and validate that the IP is not in a private or reserved range usingfilter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE). This will block requests to localhost (127.0.0.1), private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), and link-local addresses (169.254.0.0/16) while still allowing legitimate external URLs. Consider implementing a whitelist of allowed domains if the functionality only needs to support specific external services.