Understanding Replay Threats
A replay attack involves intercepting legitimate network traffic—such as HTTP requests—and retransmitting it to the server to trigger unauthorized actions. For instance, if a user submits a purchase order, an attacker could capture that packet and submit it repeatedly. This causes unintended side effects like duplicate charges or inventory depletion without the user's knowledge. In transactional systems, preventing such occurrences is critical.
Implemantation Strategy
Robust protection relies on verifying both the authenticity and the freshness of incoming requests. The standard approach combines a cryptographic signature with a strict time validation window.
Cryptographic Signing
Every request must include a digital signature generated using a shared secret key between the client and server. This ensures the payload has not been altered during transit. Since the server computes the expected hash based on known parameters and the secret key, any modification by a third party invalidates the signature verification. However, signatures alone do not prevent an attacker from capturing a valid signed message and resending it unchanged.
Temporal Validation
To address the replay vulnerability, each request includes a timestamp indicating when it was created. The server validates two conditions:
- Age Check: The request must fall within an acceptable time window (e.g., 30 seconds). Older timestamps are rejected as stale.
- Monotonicity Check: The current timestamp must be strictly greater than the previously accepted timestamp for that session or resource. This prevents exact duplicates from succeeding even within the time window.
Code Implementation
The following C# snippet demonstrates calculating a SHA-1 signature and validating the request lifecycle. Note that modern implementations often prefer HMAC-SHA256 for stronger security, but the core logic remains similar.
public static class SecurityUtils
{
/// <summary>
/// Generates a hash signature combining the timestamp and secret key.
/// </summary>
public static string ComputeHash(string timestamp, string secretKey)
{
// Concatenate parameters with a delimiter to enhance entropy
string rawInput = $"{timestamp}|{secretKey}";
using (var sha1 = System.Security.Cryptography.SHA1.Create())
{
byte[] hashBytes = sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(rawInput));
return Convert.ToHexString(hashBytes).ToLower();
}
}
/// <summary>
/// Verifies if a request is valid, fresh, and has not been replayed.
/// </summary>
public static bool ValidateRequest(string incomingTimestamp, string incomingSignature, string secretKey)
{
// 1. Verify Integrity via Signature Matching
string expectedSignature = ComputeHash(incomingTimestamp, secretKey);
if (!incomingSignature.Equals(expectedSignature, StringComparison.OrdinalIgnoreCase))
{
return false;
}
// 2. Verify Freshness (Time Window)
long unixNow = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long requestTime = long.Parse(incomingTimestamp);
TimeSpan validityWindow = TimeSpan.FromSeconds(30);
if (Math.Abs(unixNow - requestTime) > validityWindow.TotalSeconds)
{
return false;
}
// 3. Prevent Exact Replay using State Storage
// In production, use Redis or distributed cache instead of ASP.NET Session
string lastAcceptedTime = HttpContext.Current.Session["last_req_tstamp"] as string;
if (!string.IsNullOrEmpty(lastAcceptedTime))
{
long prevTime = long.Parse(lastAcceptedTime);
if (requestTime <= prevTime)
{
return false;
}
}
// Update state with new timestamp
HttpContext.Current.Session["last_req_tstamp"] = incomingTimestamp;
return true;
}
}
Tags
[ C#, Security, Replay Attack, API Protection, Authentication ]