Information Disclosure in Aspnet
How Information Disclosure Manifests in Aspnet
Information disclosure in Aspnet applications often stems from improper configuration and default behaviors that expose sensitive data. The framework's verbose error messages and diagnostic features, while helpful during development, become significant security liabilities in production.
Aspnet's default exception handling is particularly problematic. When an unhandled exception occurs, the framework generates detailed error pages containing stack traces, source file paths, and environment information. Consider this common pattern:
public IActionResult GetUserData(int id)
{
try
{
var user = _userService.GetById(id);
return Ok(user);
}
catch (Exception ex)
{
// Exposing raw exception details
return StatusCode(500, ex.Message);
}
}
This code directly returns exception messages to clients, potentially revealing database schemas, connection strings, or internal logic. An attacker could trigger specific exceptions to map the application's structure.
Another Aspnet-specific disclosure vector involves model binding errors. When complex types fail to bind, Aspnet generates detailed error responses:
public IActionResult CreateUser([FromBody] UserCreateModel model)
{
if (!ModelState.IsValid)
{
// Returns detailed model state with property names
return BadRequest(ModelState);
}
}
Attackers can exploit this to discover API contract details, field names, and validation rules. The response might reveal whether a field exists, its expected format, and even business logic constraints.
Aspnet's XML documentation and help pages present another disclosure risk. When enabled in production, these features expose method signatures, parameter descriptions, and sometimes implementation details:
// Exposed via XML docs in production
/// <summary>
/// Retrieves user by ID from SQL Server database
/// </summary>
/// <param name="userId">User identifier</param>
/// <returns>User data object</returns>
public IActionResult GetUser(int userId)
{
// Implementation
}
Directory browsing is another Aspnet-specific issue. If not explicitly disabled, attackers can enumerate application files and directories, discovering API endpoints, configuration files, and sensitive resources.
Aspnet-Specific Detection
Detecting information disclosure in Aspnet applications requires examining both configuration and runtime behavior. Start by scanning your application's configuration files for sensitive data exposure:
# Check for exposed secrets in configuration
cat appsettings.json | grep -E "(password|key|secret|token)" -A 2 -B 2
# Verify production settings
cat appsettings.Production.json
middleBrick's Aspnet-specific scanning identifies several disclosure patterns automatically. The scanner examines your application's HTTP responses for verbose error details, model state disclosures, and stack traces. It also tests for common Aspnet endpoints that might leak information:
# Scan with middleBrick CLI
middlebrick scan https://yourapi.com/api/users
# Output includes:
# - Error response analysis (stack traces, exception details)
# - Model state disclosure detection
# - Configuration exposure checks
# - Directory browsing vulnerability assessment
The scanner specifically looks for Aspnet's default diagnostic endpoints like:
/error- Detailed error pages/swagger- API documentation (if exposed in production)/health- Health check endpoints revealing system details/metrics- Performance metrics with sensitive data
middleBrick also tests for Aspnet Core's developer exception page exposure in production environments. This occurs when ASPNETCORE_ENVIRONMENT isn't properly set or when UseDeveloperExceptionPage() is accidentally called in production code paths.
Runtime monitoring complements static analysis. Tools like Application Insights or custom middleware can detect when sensitive information is being logged or returned in responses. Here's a middleware example for detection:
public class InformationDisclosureMiddleware
{
private readonly RequestDelegate _next;
private static readonly HashSet<string> SensitiveKeywords =
new() { "password", "secret", "key", "token", "connectionstring" };
public InformationDisclosureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var bodyText = await new StreamReader(context.Response.Body).ReadToEndAsync();
// Check for sensitive information in response
if (SensitiveKeywords.Any(bodyText.ToLower().Contains))
{
// Log or alert on potential disclosure
Console.WriteLine($"Potential information disclosure detected: {context.Request.Path}");
}
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
Aspnet-Specific Remediation
Remediating information disclosure in Aspnet requires both configuration changes and code-level fixes. Start with production environment hardening:
// Program.cs - Production configuration
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Ensure developer exception page is only enabled in development
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error"); // Custom error page without details
app.UseHsts();
}
// Disable directory browsing
app.UseDirectoryBrowser(enable: false);
// Remove XML documentation from production builds
app.UseSwagger(); // Remove or protect with authentication
app.UseSwaggerUI(); // Remove or protect with authentication
Implement structured exception handling that avoids information leakage:
public class CustomExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<CustomExceptionHandlerMiddleware> _logger;
public CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger<CustomExceptionHandlerMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
// Log full exception details internally
_logger.LogError(ex, "Unhandled exception at {Path}", context.Request.Path);
// Return generic error response
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = "An unexpected error occurred. Please try again later."
}));
}
}
}
Protect model state responses from information disclosure:
public IActionResult CreateUser([FromBody] UserCreateModel model)
{
if (!ModelState.IsValid)
{
// Return sanitized error response
var errors = ModelState.Values.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage ?? e.Exception?.Message)
.Where(m => !string.IsNullOrEmpty(m))
.Distinct()
.ToList();
return BadRequest(new
{
message = "Validation failed",
errors = errors.Take(5) // Limit number of errors returned
});
}
// Process valid request
return Ok();
}
Configure Aspnet Core to suppress sensitive information in logs and responses:
// appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting": "Warning"
}
},
"AllowedHosts": "*",
"Security": {
"HideSensitiveInformation": true,
"MaxErrorDetails": 0
}
}
Use Aspnet Core's built-in features for information protection:
// Configure response headers to prevent information disclosure
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
context.Response.Headers["X-Frame-Options"] = "DENY";
context.Response.Headers["X-Powered-By"] = ""; // Remove server information
return Task.CompletedTask;
});
await next();
});