Replay Attack in Aspnet with Basic Auth
Replay Attack in Aspnet with Basic Auth — how this specific combination creates or exposes the vulnerability
A replay attack occurs when an attacker intercepts a valid request and retransmits it to reproduce the intended effect. In ASP.NET APIs that rely on HTTP Basic Authentication, this risk is elevated because credentials are reused with every request once they are captured. Basic Auth encodes a username and password with Base64, which is trivial to decode, and does not inherently include protection against reuse. Without additional safeguards, an intercepted authorization header can be replayed to access protected endpoints, effectively impersonating the original user.
When an API endpoint does not enforce strict freshness controls, such as one-time use nonces or per-request timestamps, the same Basic Auth credentials can be reused indefinitely. For example, consider an endpoint that accepts an Authorization header like Authorization: Basic dXNlcjpwYXNz. An attacker who observes this header in transit can replay it to the same or another endpoint, and if the server only validates the static credentials and does not check request context, the replay succeeds.
ASP.NET applications that terminate TLS but do not enforce additional protections at the application layer remain vulnerable to this class of attack. The risk is particularly acute when requests are transmitted over insecure or shared networks where interception is feasible. Because Basic Auth does not sign or encrypt the payload, there is no built-in mechanism to detect tampering or duplication. An attacker positioned to observe even a single authenticated request can gain repeated access as long as the credentials remain valid.
Operational factors can compound the exposure. For instance, if logging inadvertently captures Authorization headers, or if monitoring tools store full request traces, the secret may be retained in logs or backups, increasing the window for replay. In distributed systems where requests pass through proxies or load balancers, inconsistent handling of headers may further weaken the security posture. The combination of weak transport assumptions and lack of per-request variability creates conditions where replay is not only possible but practical to execute.
Basic Auth-Specific Remediation in Aspnet — concrete code fixes
To mitigate replay attacks in ASP.NET when using Basic Auth, you should avoid relying on Basic Auth alone and instead layer defenses that ensure request uniqueness and freshness. The following concrete guidance and code examples assume an ASP.NET Core environment and focus on practical, implementable changes.
Require HTTPS and avoid Basic Auth for sensitive flows
Always enforce HTTPS to prevent trivial interception. Additionally, prefer token-based mechanisms (e.g., OAuth 2.0 access tokens) over Basic Auth for anything beyond trivial use cases. If you must use Basic Auth, treat the credentials as a mechanism to derive a short-lived token rather than using them for every request.
Implement request timestamp and nonce validation
Introduce per-request timestamp and nonce checks to ensure that each request is unique and recent. The server can maintain a short validity window (for example, 5 minutes) and reject requests with timestamps outside that window or with previously seen nonces.
// Example middleware for timestamp and nonce validation in ASP.NET Core
public class ReplayProtectionMiddleware
{
private readonly RequestDelegate _next;
private static readonly HashSet<string> SeenNonces = new(StringSetEqualityComparer.Ordinal);
public ReplayProtectionMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Headers.TryGetValue("X-Request-Timestamp", out var timestampHeader)
&& context.Request.Headers.TryGetValue("X-Request-Nonce", out var nonceHeader))
{
if (!long.TryParse(timestampHeader, out var timestamp))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Invalid timestamp");
return;
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
const long window = 300; // 5 minutes
if (Math.Abs(now - timestamp) > window)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Request expired");
return;
}
if (!SeenNonces.Add(nonceHeader))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Duplicate nonce");
return;
}
}
else
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Missing replay protection headers");
return;
}
await _next(context);
}
}
// Extension to register the middleware
public static class ReplayProtectionMiddlewareExtensions
{
public static IApplicationBuilder UseReplayProtection(this IApplicationBuilder builder) =>
builder.UseMiddleware<ReplayProtectionMiddleware>();
}
Use short-lived credentials derived from Basic Auth
Instead of sending the actual username and password on every request, use Basic Auth only during an initial handshake to obtain a short-lived token. The server validates Basic Auth once, then issues a scoped token with limited lifetime for subsequent requests.
// Example: Exchange Basic Auth for a short-lived token in ASP.NET Core
[ApiController]
[Route("auth")]
public class AuthController : ControllerBase
{
[HttpPost("token")]
public IActionResult Exchange([FromHeader] string authorization)
{
if (string.IsNullOrEmpty(authorization) || !authorization.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
return Unauthorized();
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTime.UtcNow:o}:{Guid.NewGuid()}"));
// Validate credentials against your user store here
// Return a short-lived token to be used in Authorization: Bearer <token>
return Ok(new { Token = token, ExpiresInSeconds = 300 });
}
}
Ensure idempotency where applicable
For operations that must be safe to retry, use idempotency keys so that repeated requests with the same key have the same effect as a single request. This does not prevent replay but reduces its impact.