HIGH ssrf server sideaspnet

Ssrf Server Side in Aspnet

How SSRF Manifests in ASP.NET

Server-Side Request Forgery (SSRF) in ASP.NET applications typically occurs when user-controlled input is used to construct URLs for internal HTTP requests without proper validation. The .NET Framework's HttpClient and WebRequest classes make it trivial for developers to make outbound requests, but this convenience creates significant security risks.

Common ASP.NET SSRF patterns include:

  • Proxy endpoints that fetch external content based on user input
  • Webhook handlers that make callback requests to attacker-controlled URLs
  • API aggregation services that consume multiple endpoints
  • PDF generation services that fetch remote resources
  • RSS feed readers and content scrapers
  • Payment gateway callbacks that trigger internal requests

Consider this vulnerable ASP.NET Core controller:

[ApiController]
[Route("[controller]")]
public class ProxyController : ControllerBase
{
    private readonly HttpClient _httpClient;

    public ProxyController(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    [HttpGet]
    public async Task<string> Get([FromQuery] string url)
    {
        // Critical vulnerability: no URL validation
        var response = await _httpClient.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
}

This endpoint allows any URL, including internal network addresses like http://localhost:8080 or cloud metadata endpoints like http://169.254.169.254/latest/meta-data/. An attacker could use this to scan internal networks, access cloud instance metadata, or trigger internal services.

Another common pattern involves file download functionality:

public async Task DownloadFile([FromQuery] string filePath)
{
    using var client = new HttpClient();
    var content = await client.GetStringAsync(filePath);
    return File(Encoding.UTF8.GetBytes(content), "text/plain", "download.txt");
}

Here, filePath might be a URL or local path, and without validation, an attacker could request file:///etc/passwd or internal network resources.

ASP.NET-Specific Detection

Detecting SSRF in ASP.NET applications requires examining both code patterns and runtime behavior. Static analysis should look for:

  • HttpClient and WebRequest usage with user-controlled parameters
  • URL construction from query parameters, form data, or headers
  • Lack of URL validation or allowlisting
  • Internal network address patterns in request destinations

Dynamic scanning with middleBrick identifies SSRF by:

  1. Sending test payloads to HTTP endpoints
  2. Attempting connections to internal network ranges (10.x, 172.16-31.x, 192.168.x)
  3. Testing cloud metadata service endpoints (169.254.169.254, 169.254.170.2)
  4. Checking for successful responses from internal addresses
  5. Analyzing response content for sensitive data exposure

middleBrick's SSRF detection specifically tests for:

Test Payload: http://localhost:5000/api/internal
Test Payload: http://169.254.169.254/latest/meta-data/
Test Payload: http://[cloud-instance-ip]/internal-api

The scanner analyzes response patterns to determine if internal services are accessible. A successful connection to localhost or internal network ranges indicates SSRF vulnerability.

For ASP.NET Core applications, also check:

  • Forwarded headers configuration that might allow IP spoofing
  • Proxy server configurations that might bypass network restrictions
  • Dependency injection configurations that provide HttpClient instances

ASP.NET-Specific Remediation

Securing ASP.NET applications against SSRF requires a defense-in-depth approach. The most effective strategy combines input validation, network controls, and safe HTTP client configuration.

1. URL Validation with Allowlisting:

public class SsrfSafeHttpClient
{
    private readonly HttpClient _httpClient;
    private readonly IAllowedUrlService _allowedUrls;

    public SsrfSafeHttpClient(IHttpClientFactory clientFactory, IAllowedUrlService allowedUrls)
    {
        _httpClient = clientFactory.CreateClient();
        _allowedUrls = allowedUrls;
    }

    public async Task<HttpResponseMessage> GetSafeAsync(string url)
    {
        if (!IsValidUrl(url))
            throw new ArgumentException("Invalid URL format");

        if (!await _allowedUrls.IsAllowedAsync(url))
            throw new ArgumentException("URL not in allowlist");

        return await _httpClient.GetAsync(url);
    }

    private bool IsValidUrl(string url)
    {
        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            return false;

        // Block private IP ranges
        if (uri.IsLoopback || uri.IsFile || IsPrivateIpAddress(uri.Host))
            return false;

        return true;
    }

    private bool IsPrivateIpAddress(string host)
    {
        if (IPAddress.TryParse(host, out var ip))
        {
            var octets = ip.GetAddressBytes();
            // 10.x.x.x
            if (octets[0] == 10) return true;
            // 172.16-31.x.x
            if (octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31) return true;
            // 192.168.x.x
            if (octets[0] == 192 && octets[1] == 168) return true;
            // 127.x.x.x loopback
            if (octets[0] == 127) return true;
        }
        return false;
    }
}

2. Using HttpClientFactory with DNS Resolution:

services.AddHttpClient<SsrfSafeHttpClient>(client =>
{
    client.Timeout = TimeSpan.FromSeconds(10);
    client.DefaultRequestHeaders.Add("User-Agent", "Secure-Client/1.0");
})
.AddHttpMessageHandler<SsrfMessageHandler>();

3. Custom Message Handler for Additional Protection:

public class SsrfMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var uri = request.RequestUri;
        
        if (uri == null)
            throw new InvalidOperationException("Request URI cannot be null");

        // Additional DNS resolution to prevent DNS rebinding
        var host = uri.Host;
        if (!IsPublicIpAddress(host))
            throw new SecurityException($"Blocked request to potentially private host: {host}");

        return await base.SendAsync(request, cancellationToken);
    }

    private bool IsPublicIpAddress(string host)
    {
        try
        {
            var ip = Dns.GetHostAddresses(host)
                .FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6);
            
            if (ip == null) return true; // Couldn't resolve, assume safe
            
            var bytes = ip.GetAddressBytes();
            
            // Check for private ranges
            if (bytes[0] == 10) return false;
            if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) return false;
            if (bytes[0] == 192 && bytes[1] == 168) return false;
            if (bytes[0] == 127) return false;
            if (bytes[0] == 169 && bytes[1] == 254) return false; // Link-local
            
            return true;
        }
        catch
        {
            return true; // DNS resolution failed, assume safe
        }
    }
}

4. Middleware for Global Protection:

public class SsrfProtectionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly SsrfSafeHttpClient _client;

    public SsrfProtectionMiddleware(RequestDelegate next, SsrfSafeHttpClient client)
    {
        _next = next;
        _client = client;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Check if this request should be blocked based on SSRF rules
        if (IsSsrfRequest(context.Request))
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsync("SSRF request blocked");
            return;
        }

        await _next(context);
    }

    private bool IsSsrfRequest(HttpRequest request)
    {
        // Check for suspicious URL patterns in query parameters
        foreach (var param in request.Query.Values)
        {
            foreach (var value in param)
            {
                if (IsSuspiciousUrl(value))
                    return true;
            }
        }
        return false;
    }

    private bool IsSuspiciousUrl(string url)
    {
        if (string.IsNullOrWhiteSpace(url)) return false;
        
        if (url.Contains("localhost", StringComparison.OrdinalIgnoreCase) ||
            url.Contains("127.0.0.1", StringComparison.OrdinalIgnoreCase))
            return true;
        
        if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
        {
            return IsPrivateIpAddress(uri.Host);
        }
        
        return false;
    }
}

5. Configuration-Based Allowlisting:

public class AllowedUrlService : IAllowedUrlService
{
    private readonly HashSet<string> _allowedDomains;
    private readonly HashSet<string> _blockedPatterns;

    public AllowedUrlService(IOptions<AllowedUrlsOptions> options)
    {
        _allowedDomains = new HashSet<string>(options.Value.AllowedDomains, StringComparer.OrdinalIgnoreCase);
        _blockedPatterns = new HashSet<string>(options.Value.BlockedPatterns, StringComparer.OrdinalIgnoreCase);
    }

    public async Task<bool> IsAllowedAsync(string url)
    {
        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            return false;

        // Check blocked patterns first
        foreach (var pattern in _blockedPatterns)
        {
            if (url.Contains(pattern, StringComparison.OrdinalIgnoreCase))
                return false;
        }

        // Check allowlist (if configured)
        if (_allowedDomains.Count > 0)
        {
            return _allowedDomains.Contains(uri.Host);
        }

        return true; // No allowlist configured, allow all
    }
}

6. Network-Level Controls:

// In Program.cs for ASP.NET Core
builder.Host.ConfigureContainer<IServiceProviderFactory>(container =>
{
    // Configure firewall rules or network policies here
    // This is conceptual - actual implementation depends on deployment environment
    NetworkPolicy.Configure();
});

The most robust approach combines these techniques: validate URLs before use, implement allowlisting for external requests, use custom HTTP message handlers for additional filtering, and deploy network-level controls to prevent internal network access from application servers.

Frequently Asked Questions

How does middleBrick detect SSRF vulnerabilities in my ASP.NET API?

middleBrick performs black-box scanning by sending test requests with various URL payloads to your API endpoints. It attempts connections to internal network ranges (10.x, 172.16-31.x, 192.168.x), cloud metadata services (169.254.169.254), and localhost addresses. If your API responds to these requests or shows signs of successful internal connections, middleBrick flags SSRF vulnerabilities. The scanner analyzes response patterns, timing, and error messages to determine if internal resources are accessible. No credentials or source code access is required - just provide your API URL and middleBrick handles the rest in 5-15 seconds.

Can SSRF in ASP.NET lead to cloud instance metadata exposure?

Yes, SSRF vulnerabilities in ASP.NET applications can expose cloud instance metadata, which often contains sensitive information like IAM roles, API keys, and instance identifiers. Cloud providers use specific IP addresses for metadata services: AWS uses 169.254.169.254, Azure uses 169.254.169.254 or 169.254.170.2, and GCP uses 169.254.169.254. An attacker can craft requests to these endpoints through your SSRF-vulnerable API to retrieve credentials, SSH keys, or other secrets. The remediation code examples above include blocking these specific IP ranges and implementing strict URL validation to prevent metadata service access.