Description
Passing a user-supplied URL to a server-side request made with HttpClient, WebRequest, or a Uri-based API can let an attacker direct requests to internal services or metadata endpoints.
Potential impact
- Internal network scanning, theft of cloud metadata, or calls to internal APIs.
Remediation
Prefer selecting destinations from a server-controlled mapping of identifiers to fixed URIs instead of accepting URLs or hosts directly. If arbitrary URLs are essential, strictly allow-list schemes, hosts, and ports; reject loopback, private, link-local, and metadata addresses across all DNS results; and disable automatic redirects or validate every redirect destination again.
Examples
Before
csharp
var response = await httpClient.GetAsync(url);
After
csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
public sealed class StatusController : ControllerBase
{
private static readonly IReadOnlyDictionary<string, Uri> Endpoints =
new Dictionary<string, Uri>(StringComparer.Ordinal)
{
["catalog"] = new Uri("https://catalog-api.example.com/health"),
["billing"] = new Uri("https://billing-api.example.com/health")
};
private static readonly HttpClient Client = new HttpClient(
new HttpClientHandler { AllowAutoRedirect = false });
[HttpGet("/status")]
public async Task<IActionResult> GetStatus([FromQuery] string service)
{
if (service == null || !Endpoints.TryGetValue(service, out Uri endpoint))
{
return BadRequest("Unknown service");
}
using HttpResponseMessage response = await Client.GetAsync(endpoint);
return StatusCode((int)response.StatusCode);
}
}
Explanation:
- Before: Sending a user-controlled URL to an outbound request API may enable SSRF.
- After: Users select only a service identifier; the actual URI comes from a fixed mapping in server code. Unknown identifiers are rejected and automatic redirects are disabled, so neither user input nor redirects can introduce another destination.