Information Disclosure on Azure
How Information Disclosure Manifests in Azure
Information disclosure in Azure environments often stems from misconfigured services that inadvertently expose sensitive data through error messages, debug endpoints, or overly verbose logging. Azure's distributed architecture creates unique attack surfaces where information can leak across service boundaries.
One common pattern occurs in Azure App Service applications that return detailed exception stack traces in production responses. When an unhandled exception occurs, the default behavior may expose internal file paths, database connection strings, or even Azure-specific environment variables. Consider this vulnerable ASP.NET Core controller:
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetOrder(int id)
{
try
{
// Simulated database access
var order = _orderRepository.GetById(id);
if (order == null) return NotFound();
return Ok(order);
}
catch (Exception ex)
{
// Vulnerable: returns full exception details
return StatusCode(500, new {
Error = ex.Message,
StackTrace = ex.StackTrace
});
}
}
}
This exposes Azure-specific paths like C:\\home\\site\\wwwroot and potentially connection strings when exceptions occur.
Azure Blob Storage misconfigurations represent another significant vector. When anonymous access is enabled on containers, attackers can enumerate all blobs and potentially discover sensitive files. The following configuration would allow public access:
// Vulnerable: anonymous access enabled
BlobContainerClient container = new BlobContainerClient(
connectionString: "",
containerName: "public-data"
);
await container.SetAccessPolicyAsync(PublicAccessType.Blob);
Azure Functions also present unique disclosure risks through HTTP trigger bindings. Default function configurations may expose internal function names, execution times, and Azure Functions runtime versions in response headers:
// Vulnerable: exposes internal details
[FunctionName("OrderProcessor")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
ILogger log)
{
// Function name and runtime details may be exposed
return new OkObjectResult("Processing complete");
}
Azure API Management instances often leak information through improperly configured policies. A common mistake is enabling detailed error responses in production:
<policies>
<!-- Vulnerable: detailed errors in production -->
<backend>
<forward-request />
</backend>
<outbound>
<send-response />
</outbound>
<on-error>
<return-response>
<status code="500" reason="Internal Server Error" />
<set-body>
<![CDATA[{"error": "@{(string)context.LastError.Message}"}]]>
</set-body>
</return-response>
</on-error>
</policies>
Azure Key Vault presents another attack surface where misconfigured access policies can lead to credential exposure. If a web application has excessive Key Vault permissions, a successful attack could enumerate all secrets:
// Vulnerable: overly permissive Key Vault access
var kvClient = new SecretClient(
new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential()
);
// Attacker could enumerate all secrets if permissions allow
var secrets = kvClient.GetPropertiesOfSecrets().ToList();
foreach (var secret in secrets)
{
var secretValue = kvClient.GetSecret(secret.Name);
Console.WriteLine($"{secret.Name}: {secretValue.Value}");
}
Azure-Specific Detection
Detecting information disclosure in Azure environments requires both automated scanning and manual inspection of Azure-specific configurations. Azure provides several built-in tools, but comprehensive detection often requires specialized security scanners.
Azure Security Center (ASC) can identify some information disclosure risks through its security policies. For example, it can detect when Blob Storage containers have public access enabled:
# Azure CLI command to check for public blob access
az storage container show-permission \
--account-name mystorageaccount \
--name mycontainer
# Output interpretation
# If "publicAccess": "Container" or "Blob", this is a finding
For application-level detection, Azure App Service provides diagnostic logging that can reveal information disclosure patterns. Enable Application Logging and Failed Request Tracing to capture detailed error information:
# Azure CLI to enable diagnostic logs
az webapp log config \
--name myapp \
--resource-group mygroup \
--application-logging true \
--level information
# Check for verbose error messages in logs
az webapp log tail --name myapp --resource-group mygroup
middleBrick provides specialized Azure information disclosure scanning that tests the unauthenticated attack surface of Azure-hosted APIs. It identifies Azure-specific disclosure patterns including:
| Check Type | Azure-Specific Target | Detection Method |
|---|---|---|
| Stack Trace Analysis | .NET Core exception responses | Regex patterns for Azure paths, connection strings |
| Header Disclosure | Server, X-AspNet-Version headers | Response header analysis |
| Blob Storage Enumeration | Anonymous container access | HEAD requests to container URLs |
| API Management Policies | Error response configurations | Policy XML analysis |
| Function Runtime Info | Azure Functions metadata | Trigger endpoint analysis |
The middleBrick CLI provides Azure-specific scanning capabilities:
# Install middleBrick CLI
npm install -g middlebrick
# Scan an Azure-hosted API
middlebrick scan https://myazureapp.azurewebsites.net/api/orders
# Output includes Azure-specific findings
{
"azureInfoDisclosure": {
"found": true,
"severity": "high",
"description": "Stack trace contains Azure-specific file paths",
"remediation": "Implement custom error handling middleware"
}
}
For Azure Functions, you can use the Azure Functions Core Tools to test for information disclosure:
# Test function locally for verbose error messages
func start --verbose
# Look for:
# - Internal runtime paths
# - Azure Functions version information
# - Connection string exposure in logs
Azure-Specific Remediation
Remediating information disclosure in Azure requires a combination of application-level fixes and Azure service configuration changes. The following approaches address Azure-specific disclosure patterns.
For ASP.NET Core applications hosted in Azure App Service, implement centralized exception handling with custom error pages:
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// Production: custom error handling without sensitive details
app.UseExceptionHandler("/error");
// Prevent stack trace disclosure
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception)
{
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."
})
);
}
});
}
}
}
Configure Azure Blob Storage with least-privilege access policies:
// Secure Blob Storage configuration
BlobContainerClient container = new BlobContainerClient(
connectionString: "",
containerName: "secure-data"
);
// Explicitly disable public access
await container.SetAccessPolicyAsync(PublicAccessType.None);
// Use Azure AD authentication instead of shared keys
BlobClientOptions options = new BlobClientOptions
{
Credential = new DefaultAzureCredential()
};
// Implement fine-grained access control
await container.SetAccessPolicyAsync(new BlobContainerAccessPolicy
{
Permissions = "r" // Read-only for specific identities
});
For Azure API Management, configure policies to prevent information disclosure:
<policies>
<!-- Secure error handling -->
<inbound>
<set-header name="X-Content-Type-Options" exists-action="override">
<value>nosniff</value>
</set-header>
<set-header name="X-Frame-Options" exists-action="override">
<value>SAMEORIGIN</value>
</set-header>
</inbound>
<outbound>
<!-- Remove server headers -->
<set-header name="Server" exists-action="delete" />
<set-header name="X-AspNet-Version" exists-action="delete" />
<set-header name="X-Powered-By" exists-action="delete" />
</outbound>
<on-error>
<!-- Generic error response without details -->
<return-response>
<set-status code="500" reason="Internal Server Error" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>
<![CDATA[{"error": "An internal error occurred. Please contact support with reference ID: @{(string)context.OperationId}"}]]>
</set-body>
</return-response>
</on-error>
</policies>
Secure Azure Functions by configuring proper authorization levels and removing debug information:
// Secure Azure Function configuration
[FunctionName("SecureOrderProcessor")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequestMessage req,
ILogger log,
TraceWriter trace)
{
try
{
// Process request
var data = await req.Content.ReadAsStringAsync();
// Return generic success response
return req.CreateResponse(HttpStatusCode.OK, new
{
status = "success",
message = "Order processed successfully"
});
}
catch (Exception ex)
{
// Log error without exposing details
log.LogError(ex, "Error processing order");
// Return generic error without stack trace
return req.CreateErrorResponse(HttpStatusCode.InternalServerError,
"An error occurred while processing your request");
}
}
Implement Azure Key Vault with least-privilege access patterns:
// Secure Key Vault access with managed identities
var kvClient = new SecretClient(
new Uri("https://myapp-vault.vault.azure.net/"),
new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
// Only allow specific identities
ExcludeSharedTokenCacheCredential = true,
ExcludeVisualStudioCredential = true
})
);
// Use Azure AD authentication instead of secrets
var credential = new DefaultAzureCredential();
var client = new BlobServiceClient(
new Uri("https://myaccount.blob.core.windows.net"),
credential
);
// Implement secret rotation policies
await kvClient.SetSecretAsync("ConnectionString",
"",
new BlobHttpHeaders { ContentType = "application/json" });