Ssrf Blind in Aspnet
How Blind SSRF Manifests in ASP.NET
Blind Server-Side Request Forgery (SSRF) occurs when an attacker can induce an ASP.NET application to make HTTP requests to arbitrary URLs but cannot directly view the response. Instead, attackers rely on side channels like response timing, error messages, or out-of-band (OAST) indicators to extract information. In ASP.NET, this commonly arises when user-supplied URLs are passed to HttpClient or IHttpClientFactory without validation, particularly in scenarios where the response is not returned to the user.
A typical vulnerable pattern in ASP.NET Core controllers looks like this:
[HttpPost("api/webhook")]
public async Task RegisterWebhook([FromBody] WebhookRequest request)
{
// Vulnerable: user-controlled URL used without validation
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.PostAsync(request.Url,
new StringContent("test"));
// Only a generic status is returned; response body is hidden
return Ok(new { status = "registered" });
} Here, an attacker can supply request.Url pointing to internal infrastructure (e.g., http://169.254.169.254/latest/meta-data/ on AWS) or other cloud provider metadata endpoints. Because the response isn't exposed, the attack is "blind," but timing differences or error messages (e.g., 403 vs 200) can reveal information about internal services. Another common vector is URL parameters in GET endpoints that trigger server-side fetches:
[HttpGet("api/fetch")]
public async Task FetchData([FromQuery] string target)
{
// Blind SSRF: response not returned, but request is made
var client = _httpClientFactory.CreateClient("ExternalService");
await client.GetAsync(target); // No response read
return Ok(new { message = "Request processed" });
} ASP.NET applications often use HttpClient with default credentials or proxy settings inherited from the host environment. If an attacker can reach the metadata service (e.g., http://169.254.169.254 on AWS, http://169.254.169.254/metadata/instance on Azure), they may steal IAM credentials. Blind SSRF can also probe internal network topology by observing latency changes when requesting http://internal-host:port.
ASP.NET-Specific Detection
Detecting blind SSRF in ASP.NET requires out-of-band techniques since the response isn't visible in the HTTP response. middleBrick automates this by injecting unique, attacker-controlled URLs into every parameter that might be used for server-side requests (identified via OpenAPI spec analysis or heuristic scanning). When the ASP.NET application makes a request to one of these URLs, middleBrick's monitoring infrastructure logs the inbound connection, confirming SSRF vulnerability.
For ASP.NET APIs, middleBrick specifically tests:
- All string parameters in GET/POST bodies that resemble URLs (e.g.,
url,endpoint,callback,redirect_uri). - OpenAPI/Swagger definitions with
format: uriorformat: url. - Common ASP.NET patterns like webhook registration endpoints, proxy controllers, and OAuth
redirect_uriimplementations.
Example scan command using middleBrick's CLI:
middlebrick scan https://api.example.com/swagger/v1/swagger.jsonmiddleBrick will submit payloads like http:// to vulnerable parameters. If the ASP.NET app initiates a request to that domain, middleBrick detects it via DNS/HTTP logs. The scanner also checks for cloud metadata endpoint access attempts (e.g., http://169.254.169.254/latest/meta-data/) and reports them as high-severity findings. Because middleBrick resolves all $ref in OpenAPI specs, it accurately identifies every URL-accepting parameter, even in complex ASP.NET Core API projects with nested definitions.
ASP.NET-Specific Remediation
Remediating blind SSRF in ASP.NET requires strict validation of any user-supplied URL before it is used with HttpClient. The primary defenses are:
- Allowlist permitted schemes and domains: Only allow
httpandhttps, and restrict to known external domains. - Block private IP ranges and cloud metadata endpoints: Prevent requests to
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, and169.254.169.254. - Use a dedicated
HttpMessageHandlerto enforce validation at the HTTP client level.
Here is a robust validation helper for ASP.NET Core:
public static class SsrfValidator
{
private static readonly HashSet AllowedSchemes = new() { "http", "https" };
private static readonly byte[][] PrivateRanges =
{
new byte[] { 10, 0, 0, 0 }, new byte[] { 10, 255, 255, 255 }, // 10.0.0.0/8
new byte[] { 172, 16, 0, 0 }, new byte[] { 172, 31, 255, 255 }, // 172.16.0.0/12
new byte[] { 192, 168, 0, 0 }, new byte[] { 192, 168, 255, 255 }, // 192.168.0.0/16
new byte[] { 127, 0, 0, 0 }, new byte[] { 127, 255, 255, 255 } // 127.0.0.0/8
};
public static bool IsSafeUrl(string url, out string error)
{
error = string.Empty;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
error = "Invalid URL format";
return false;
}
if (!AllowedSchemes.Contains(uri.Scheme.ToLowerInvariant()))
{
error = "Only HTTP/HTTPS schemes allowed";
return false;
}
// Block cloud metadata endpoints explicitly
if (uri.Host.Equals("169.254.169.254", StringComparison.OrdinalIgnoreCase))
{
error = "Access to metadata endpoint blocked";
return false;
}
try
{
var addresses = Dns.GetHostAddresses(uri.Host);
foreach (var ip in addresses)
{
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
continue;
var bytes = ip.GetAddressBytes();
if (IsPrivateIp(bytes))
{
error = "Private IP ranges are not allowed";
return false;
}
}
}
catch
{
error = "DNS resolution failed";
return false; // Fail-safe on DNS errors
}
return true;
}
private static bool IsPrivateIp(byte[] bytes)
{
// Simple range check for IPv4
if (bytes[0] == 10) return true;
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) return true;
if (bytes[0] == 192 && bytes[1] == 168) return true;
if (bytes[0] == 127) return true;
return false;
}
} Use this validator in your controller before making any HttpClient call:
[HttpPost("api/webhook")]
public async Task RegisterWebhook([FromBody] WebhookRequest request)
{
if (!SsrfValidator.IsSafeUrl(request.Url, out var error))
{
return BadRequest(new { error = $"Invalid URL: {error}" });
}
var client = _httpClientFactory.CreateClient();
await client.PostAsync(request.Url, new StringContent("test"));
return Ok(new { status = "registered" });
} For higher assurance, create a custom DelegatingHandler that validates every outgoing request URI:
public class SsrfProtectionHandler : DelegatingHandler
{
protected override async Task SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!SsrfValidator.IsSafeUrl(request.RequestUri.ToString(), out var error))
{
throw new InvalidOperationException($"Blocked SSRF attempt: {error}");
}
return await base.SendAsync(request, cancellationToken);
}
}
// In Program.cs:
services.AddHttpClient("SecureClient")
.AddHttpMessageHandler(); This ensures all outgoing requests from that client are validated, even if called from deeper layers. Note that DNS rebinding attacks can bypass host-based checks; consider using a library like Microsoft.AspNetCore.Http.Features to further restrict connections, but the core principle is to never trust user-supplied URLs.
Frequently Asked Questions
What is blind SSRF and how does it differ from regular SSRF?
Why are ASP.NET applications particularly vulnerable to SSRF?
HttpClient to integrate with external services, and it's common to accept URLs from user input (e.g., webhook endpoints, proxy features, OAuth callbacks). Developers may assume that because the response isn't returned to the user, the risk is low. However, blind SSRF can still access cloud metadata endpoints (like AWS's 169.254.169.254) or scan internal infrastructure via timing. The widespread use of IHttpClientFactory doesn't inherently prevent SSRF—validation must be explicit.