Added more security

This commit is contained in:
2026-06-24 06:47:13 +02:00
committed by Jeroen Saey
parent e9a815dd2a
commit 2c5b69bf2c
6 changed files with 638 additions and 12 deletions
+65 -6
View File
@@ -19,6 +19,17 @@ namespace EonaCat.Connections.Helpers
private volatile bool _running;
private readonly Func<string> _getHealthJson;
private readonly Func<string> _getStatusJson;
private int _maxRequestSize = 10 * 1024 * 1024; // 10 MB default
/// <summary>
/// Gets or sets the maximum HTTP request size in bytes.
/// Defaults to 10 MB. Set before calling Start().
/// </summary>
public int MaxRequestSize
{
get => _maxRequestSize;
set => _maxRequestSize = Math.Max(1024, value); // Minimum 1 KB
}
/// <summary>
/// Gets the actual port the server is listening on.
@@ -202,13 +213,14 @@ namespace EonaCat.Connections.Helpers
}
}
private static async Task<string> ReadRequestLineAsync(NetworkStream stream)
private async Task<string> ReadRequestLineAsync(NetworkStream stream)
{
var sb = new StringBuilder();
var buffer = new byte[1];
var prev = (byte)0;
int maxLineSize = Math.Min(8192, _maxRequestSize); // Limit request line size
while (sb.Length < 8192)
while (sb.Length < maxLineSize)
{
int read = await stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false);
if (read == 0)
@@ -222,6 +234,12 @@ namespace EonaCat.Connections.Helpers
break;
}
// Security: Reject null bytes and other control characters that shouldn't be in HTTP headers
if (b < 32 && b != (byte)'\r' && b != (byte)'\t')
{
throw new InvalidOperationException("Invalid character in HTTP request line");
}
if (b != (byte)'\r')
{
sb.Append((char)b);
@@ -230,16 +248,24 @@ namespace EonaCat.Connections.Helpers
prev = b;
}
// Security: Check if request line exceeds limit
if (sb.Length >= maxLineSize)
{
throw new InvalidOperationException("HTTP request line exceeds maximum size");
}
return sb.ToString();
}
private static async Task DrainHeadersAsync(NetworkStream stream)
private async Task DrainHeadersAsync(NetworkStream stream)
{
var sb = new StringBuilder();
var buffer = new byte[1];
int consecutiveNewlines = 0;
int totalBytesRead = 0;
int maxHeaderSize = Math.Min(65536, _maxRequestSize); // Limit header size to 64 KB
while (consecutiveNewlines < 2)
while (consecutiveNewlines < 2 && totalBytesRead < maxHeaderSize)
{
int read = await stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false);
if (read == 0)
@@ -247,6 +273,14 @@ namespace EonaCat.Connections.Helpers
break;
}
totalBytesRead++;
// Security: Reject null bytes in headers
if (buffer[0] == 0)
{
throw new InvalidOperationException("Null byte in HTTP headers");
}
if (buffer[0] == (byte)'\n')
{
consecutiveNewlines++;
@@ -256,18 +290,43 @@ namespace EonaCat.Connections.Helpers
consecutiveNewlines = 0;
}
}
// Security: Check if headers exceed limit
if (totalBytesRead >= maxHeaderSize)
{
throw new InvalidOperationException("HTTP headers exceed maximum size");
}
}
private static async Task WriteResponseAsync(NetworkStream stream, int statusCode, string statusText, string contentType, string body)
private async Task WriteResponseAsync(NetworkStream stream, int statusCode, string statusText, string contentType, string body)
{
var bodyBytes = Encoding.UTF8.GetBytes(body);
// Security: Enforce maximum response size
if (bodyBytes.Length > _maxRequestSize)
{
// Send error response instead
bodyBytes = Encoding.UTF8.GetBytes("{\"error\":\"Response too large\"}");
}
// Security: Validate content type to prevent injection
if (!string.IsNullOrEmpty(contentType) &&
(contentType.Contains("\r") || contentType.Contains("\n") || contentType.Contains("\0")))
{
contentType = "application/json";
}
var header = $"HTTP/1.1 {statusCode} {statusText}\r\n" +
$"Content-Type: {contentType}; charset=utf-8\r\n" +
$"Content-Length: {bodyBytes.Length}\r\n" +
"Access-Control-Allow-Origin: *\r\n" +
"Connection: close\r\n" +
"X-Content-Type-Options: nosniff\r\n" +
"Cache-Control: no-store\r\n" +
"X-Frame-Options: DENY\r\n" +
"X-XSS-Protection: 1; mode=block\r\n" +
"Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" +
"Cache-Control: no-store, no-cache, must-revalidate, private\r\n" +
"Pragma: no-cache\r\n" +
"\r\n";
var headerBytes = Encoding.ASCII.GetBytes(header);
@@ -0,0 +1,253 @@
using System;
using System.Collections.Concurrent;
namespace EonaCat.Connections.Helpers
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/license for full license details.
/// <summary>
/// Provides security validation utilities to protect against common network attacks
/// including buffer overflows, memory exhaustion (decompression bombs), malformed packets,
/// and injection attacks.
/// </summary>
public static class SecurityValidator
{
private static readonly ConcurrentDictionary<string, RateLimitEntry> _rateLimitCache =
new ConcurrentDictionary<string, RateLimitEntry>();
private class RateLimitEntry
{
public long RequestCount;
public DateTime WindowStart;
}
/// <summary>
/// Validates a length prefix value against configuration limits.
/// Prevents buffer overflow and memory exhaustion attacks by:
/// - Rejecting negative or zero lengths
/// - Rejecting lengths exceeding configured maximum
/// - Rejecting obviously malformed values (e.g., impossibly large lengths)
/// </summary>
public static bool ValidateLengthPrefix(long messageLengthValue, int maxMessageSize)
{
// Reject negative, zero, or non-positive values
if (messageLengthValue <= 0)
{
return false;
}
// Reject values exceeding maximum allowed message size
if (messageLengthValue > maxMessageSize)
{
return false;
}
// Reject impossibly large values that could indicate malformed data
if (messageLengthValue > int.MaxValue)
{
return false;
}
return true;
}
/// <summary>
/// Validates message stream size to prevent memory exhaustion attacks.
/// Checks if accumulated message data exceeds configured thresholds.
/// </summary>
public static bool ValidateMessageStreamSize(long currentStreamLength, long maxStreamSize)
{
if (currentStreamLength <= 0 || maxStreamSize <= 0)
{
return true; // No limit configured
}
return currentStreamLength < maxStreamSize;
}
/// <summary>
/// Validates UDP packet size to prevent buffer overflow attacks.
/// </summary>
public static bool ValidateUdpPacketSize(int receivedBytes, int bufferSize)
{
// UDP packet should not exceed buffer size
if (receivedBytes <= 0 || receivedBytes > bufferSize)
{
return false;
}
return true;
}
/// <summary>
/// Implements basic rate limiting per source IP address.
/// Returns true if request is within rate limit, false if limit exceeded.
/// </summary>
public static bool CheckRateLimit(string sourceAddress, int maxRequestsPerSecond, int windowSizeSeconds = 1)
{
if (string.IsNullOrEmpty(sourceAddress) || maxRequestsPerSecond <= 0)
{
return true; // No rate limit configured
}
var now = DateTime.UtcNow;
var entry = _rateLimitCache.AddOrUpdate(sourceAddress,
new RateLimitEntry { RequestCount = 1, WindowStart = now },
(key, existing) =>
{
// Check if we're still in the same window
var timeDiff = now - existing.WindowStart;
if (timeDiff.TotalSeconds < windowSizeSeconds)
{
existing.RequestCount++;
}
else
{
// Reset window
existing.RequestCount = 1;
existing.WindowStart = now;
}
return existing;
});
return entry.RequestCount <= maxRequestsPerSecond;
}
/// <summary>
/// Validates HTTP header names and values to prevent header injection attacks.
/// </summary>
public static bool ValidateHttpHeader(string headerName, string headerValue)
{
if (string.IsNullOrEmpty(headerName) || string.IsNullOrEmpty(headerValue))
{
return false;
}
// Check for newline characters that could be used in HTTP header injection
if (headerName.Contains("\r") || headerName.Contains("\n") ||
headerValue.Contains("\r") || headerValue.Contains("\n"))
{
return false;
}
// Check for null bytes
if (headerName.Contains("\0") || headerValue.Contains("\0"))
{
return false;
}
return true;
}
/// <summary>
/// Validates that a message doesn't contain null bytes which could be used for injection attacks.
/// </summary>
public static bool ValidateMessageContent(byte[] data)
{
if (data == null || data.Length == 0)
{
return true; // Empty is generally OK
}
// This is optional depending on your protocol - some protocols may legitimately use null bytes
// For now, we'll allow them but this can be made stricter if needed
return true;
}
/// <summary>
/// Validates delimiter to prevent delimiter injection attacks.
/// </summary>
public static bool ValidateDelimiter(byte[] delimiter)
{
if (delimiter == null || delimiter.Length == 0)
{
return false;
}
// Delimiter should be reasonably small
if (delimiter.Length > 256)
{
return false;
}
return true;
}
/// <summary>
/// Detects potential decompression bomb by checking if uncompressed size ratio is too high.
/// </summary>
public static bool ValidateCompressionRatio(long compressedSize, long decompressedSize, double maxRatio = 100.0)
{
if (compressedSize <= 0 || decompressedSize <= 0)
{
return true; // Can't validate
}
double ratio = (double)decompressedSize / compressedSize;
return ratio <= maxRatio;
}
/// <summary>
/// Cleans up old rate limit entries to prevent memory leaks.
/// Should be called periodically from cleanup/maintenance tasks.
/// </summary>
public static void CleanupRateLimitCache(int maxAgeSeconds = 300)
{
var now = DateTime.UtcNow;
var keysToRemove = new System.Collections.Generic.List<string>();
foreach (var kvp in _rateLimitCache)
{
var age = now - kvp.Value.WindowStart;
if (age.TotalSeconds > maxAgeSeconds)
{
keysToRemove.Add(kvp.Key);
}
}
foreach (var key in keysToRemove)
{
_rateLimitCache.TryRemove(key, out _);
}
}
/// <summary>
/// Sanitizes error messages to prevent information leakage.
/// </summary>
public static string SanitizeErrorMessage(string message, bool isInternal = false)
{
if (string.IsNullOrEmpty(message))
{
return "An error occurred";
}
if (isInternal)
{
// Internal logs can include full details
return message;
}
// External error messages should be generic to avoid info leakage
// Only include safe error information back to clients
if (message.Contains("socket", StringComparison.OrdinalIgnoreCase) ||
message.Contains("connection", StringComparison.OrdinalIgnoreCase))
{
return "Connection error occurred";
}
if (message.Contains("timeout", StringComparison.OrdinalIgnoreCase))
{
return "Operation timed out";
}
if (message.Contains("invalid", StringComparison.OrdinalIgnoreCase) ||
message.Contains("malformed", StringComparison.OrdinalIgnoreCase))
{
return "Invalid data received";
}
return "An error occurred";
}
}
}
@@ -159,6 +159,56 @@ namespace EonaCat.Connections.Models
/// </summary>
public double ReadTimeoutSeconds { get; set; } = 300;
// Security Settings (Protection against network attacks)
/// <summary>
/// Maximum accumulated message stream size in bytes before considering it a potential memory exhaustion attack.
/// Prevents decompression bombs and message accumulation attacks. (default: 500 MB)
/// </summary>
public long MaxMessageStreamSize { get; set; } = 500 * 1024 * 1024;
/// <summary>
/// Maximum number of messages allowed per client per second. 0 means unlimited. (default: 0)
/// Helps prevent flood/DoS attacks.
/// </summary>
public int MaxMessagesPerSecond { get; set; } = 0;
/// <summary>
/// Enable strict validation of length-prefixed messages to detect malformed packets. (default: true)
/// </summary>
public bool EnableLengthPrefixValidation { get; set; } = true;
/// <summary>
/// Enable rate limiting for UDP packets. 0 means unlimited. (default: 0)
/// </summary>
public int MaxUdpPacketsPerSecond { get; set; } = 0;
/// <summary>
/// Maximum HTTP request/response size for health API in bytes. (default: 10 MB)
/// </summary>
public int MaxHealthApiRequestSize { get; set; } = 10 * 1024 * 1024;
/// <summary>
/// Enable sanitization of error messages to prevent information leakage. (default: true)
/// When enabled, error details are hidden from clients.
/// </summary>
public bool EnableErrorMessageSanitization { get; set; } = true;
/// <summary>
/// Enable automatic cleanup of rate limit cache. (default: true)
/// </summary>
public bool EnableRateLimitCacheCleanup { get; set; } = true;
/// <summary>
/// Interval in seconds for cleaning up rate limit cache. (default: 300)
/// </summary>
public int RateLimitCacheCleanupIntervalSeconds { get; set; } = 300;
/// <summary>
/// Enable validation of HTTP headers to prevent header injection attacks. (default: true)
/// </summary>
public bool EnableHttpHeaderValidation { get; set; } = true;
internal RemoteCertificateValidationCallback GetRemoteCertificateValidationCallback()
{
return CertificateValidation;
+26 -3
View File
@@ -1209,9 +1209,10 @@ namespace EonaCat.Connections
long length = ParseLengthPrefix(lengthBuffer, prefixSize, _config.UseBigEndian);
if (length <= 0 || length > _config.MAX_MESSAGE_SIZE)
// Security: Validate length prefix using SecurityValidator
if (!Helpers.SecurityValidator.ValidateLengthPrefix(length, _config.MAX_MESSAGE_SIZE))
{
throw new InvalidOperationException($"Invalid message length {length}");
throw new InvalidOperationException($"Invalid message length {length}. Expected value between 1 and {_config.MAX_MESSAGE_SIZE}.");
}
byte[] buffer = ArrayPool<byte>.Shared.Rent((int)length);
@@ -1244,10 +1245,18 @@ namespace EonaCat.Connections
RecordError(ex, "Read timeout during length-prefixed receive - stream may have hung");
await DisconnectClientAsync(DisconnectReason.Timeout, ex);
}
catch (InvalidOperationException invOpEx) when (invOpEx.Message.Contains("Invalid message length"))
{
// Malformed message length - security issue
DebugConnection($"Malformed message length detected: {invOpEx.Message}");
RecordError(invOpEx, $"Malformed message length: {invOpEx.Message}");
await DisconnectClientAsync(DisconnectReason.Error, invOpEx);
}
catch (SocketException socketEx)
{
DebugConnection($"Socket error in length-prefixed receive: {socketEx.SocketErrorCode} - {socketEx.Message}");
RecordError(socketEx, $"Socket error in length-prefixed receive: {socketEx.SocketErrorCode} - {socketEx.SocketErrorCode switch { SocketError.ConnectionReset => "Connection reset by remote", SocketError.ConnectionAborted => "Connection aborted", _ => "Unknown socket error" }}");
var reason = socketEx.SocketErrorCode == SocketError.ConnectionReset || socketEx.SocketErrorCode == SocketError.ConnectionAborted
? DisconnectReason.RemoteClosed
: DisconnectReason.Error;
@@ -1418,9 +1427,16 @@ namespace EonaCat.Connections
memory.Write(buffer, 0, read);
// Security: Check both individual message size and accumulated stream size
if (memory.Length > _config.MAX_MESSAGE_SIZE)
{
throw new InvalidOperationException("Message too large.");
throw new InvalidOperationException($"Message too large: {memory.Length} > {_config.MAX_MESSAGE_SIZE}");
}
// Additional security: Enforce max accumulated message stream size
if (_config.MaxMessageStreamSize > 0 && memory.Length > _config.MaxMessageStreamSize)
{
throw new InvalidOperationException($"Message buffer exceeded maximum size of {_config.MaxMessageStreamSize} bytes.");
}
while (TryExtractMessage(memory, _config.Delimiter, out var message))
@@ -1441,6 +1457,13 @@ namespace EonaCat.Connections
RecordError(ex, "Read timeout in delimiter receive - stream may be unresponsive");
await DisconnectClientAsync(DisconnectReason.Timeout, ex);
}
catch (InvalidOperationException invOpEx) when (invOpEx.Message.Contains("Message"))
{
// Message size violation - security issue
DebugConnection($"Message size limit violation: {invOpEx.Message}");
RecordError(invOpEx, $"Message size limit violated: {invOpEx.Message}");
await DisconnectClientAsync(DisconnectReason.Error, invOpEx);
}
catch (SocketException socketEx)
{
DebugConnection($"Socket error in delimiter receive: {socketEx.SocketErrorCode} - {socketEx.Message}");
+76 -3
View File
@@ -303,6 +303,12 @@ namespace EonaCat.Connections
_pongTask = Task.Run(() => StartPongLoopAsync(_serverCancellation.Token), _serverCancellation.Token);
}
// Start rate limit cache cleanup if enabled
if (_config.EnableRateLimitCacheCleanup)
{
_ = Task.Run(() => CleanupRateLimitCacheAsync(_serverCancellation.Token), _serverCancellation.Token);
}
if (_config.EnableAutoHtmlReports)
{
StatusPage.StartAutoHtmlReport(
@@ -1111,6 +1117,31 @@ namespace EonaCat.Connections
}
}
private async Task CleanupRateLimitCacheAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
// Periodically clean up old rate limit entries to prevent memory leaks
Helpers.SecurityValidator.CleanupRateLimitCache(_config.RateLimitCacheCleanupIntervalSeconds);
}
catch (Exception exception)
{
DebugConnection($"Rate limit cache cleanup error: {exception.Message}");
}
try
{
await Task.Delay(TimeSpan.FromSeconds(_config.RateLimitCacheCleanupIntervalSeconds), token);
}
catch (OperationCanceledException)
{
break;
}
}
}
private async Task CleanupUdpClientsAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
@@ -1131,6 +1162,22 @@ namespace EonaCat.Connections
{
var clientKey = result.RemoteEndPoint.ToString();
// Security: Validate UDP packet size
if (result.Buffer.Length <= 0 || result.Buffer.Length > _config.BufferSize)
{
RecordError(null, $"UDP packet size validation failed: {result.Buffer.Length} not in range [1, {_config.BufferSize}]", clientKey, null);
return;
}
// Security: Rate limiting for UDP packets
if (_config.MaxUdpPacketsPerSecond > 0 &&
!Helpers.SecurityValidator.CheckRateLimit(clientKey, _config.MaxUdpPacketsPerSecond))
{
DebugConnection($"UDP rate limit exceeded for {clientKey}, packet dropped.");
_stats.IncrementDroppedPackets();
return;
}
if (!_clients.TryGetValue(clientKey, out var client))
{
client = new Connection
@@ -1171,7 +1218,7 @@ namespace EonaCat.Connections
while (!token.IsCancellationRequested && client.IsConnected && stream != null)
{
int bytesRead = 0;
try
{
bytesRead = await stream.ReadAsync(readBuffer, 0, _config.BufferSize, token);
@@ -1222,6 +1269,17 @@ namespace EonaCat.Connections
return;
}
// Security check: Validate message stream doesn't exceed maximum safe size
long newStreamLength = messageStream.Length + bytesRead;
if (_config.MaxMessageStreamSize > 0 &&
newStreamLength > _config.MaxMessageStreamSize)
{
DebugConnection($"Message buffer exceeded maximum size ({_config.MaxMessageStreamSize} bytes) for client {client.Nickname} ({client.Id}), disconnecting.");
RecordError(null, $"Message buffer size exceeded for client: {newStreamLength} > {_config.MaxMessageStreamSize}", client.Id, client.Nickname);
await DisconnectClientAsync(client.Id, DisconnectReason.Error);
return;
}
UpdateLastActive(client);
await messageStream.WriteAsync(readBuffer, 0, bytesRead, token);
@@ -1303,6 +1361,13 @@ namespace EonaCat.Connections
RecordError(ioEx, $"IO error in communication loop: {ioEx.Message}", client.Id, client.Nickname);
await DisconnectClientAsync(client.Id, DisconnectReason.Error, ioEx);
}
catch (InvalidOperationException invOpEx) when (invOpEx.Message.Contains("Invalid message length"))
{
// Malformed message length - security issue
DebugConnection($"Malformed message length detected for client {client.Nickname} ({client.Id}): {invOpEx.Message}, disconnecting.");
RecordError(invOpEx, $"Malformed message length: {invOpEx.Message}", client.Id, client.Nickname);
await DisconnectClientAsync(client.Id, DisconnectReason.Error);
}
catch (Exception ex)
{
DebugConnection($"Error in communication loop for client {client.Nickname} ({client.Id}): {ex.Message}, Disconnecting");
@@ -1331,9 +1396,10 @@ namespace EonaCat.Connections
long messageLengthValue = ParseLengthPrefix(buffer, prefixSize, _config.UseBigEndian);
if (messageLengthValue <= 0 || messageLengthValue > int.MaxValue)
// Validate length prefix using SecurityValidator
if (!Helpers.SecurityValidator.ValidateLengthPrefix(messageLengthValue, _config.MAX_MESSAGE_SIZE))
{
throw new InvalidOperationException("Invalid message length.");
throw new InvalidOperationException($"Invalid message length: {messageLengthValue}. Expected value between 1 and {_config.MAX_MESSAGE_SIZE}.");
}
int messageLength = (int)messageLengthValue;
@@ -1343,6 +1409,13 @@ namespace EonaCat.Connections
return false;
}
// Check if message stream would exceed maximum safe size (prevents memory exhaustion attacks)
if (_config.MaxMessageStreamSize > 0 &&
length - prefixSize - messageLength > _config.MaxMessageStreamSize)
{
throw new InvalidOperationException($"Message buffer exceeded maximum size of {_config.MaxMessageStreamSize} bytes.");
}
message = new byte[messageLength];
Buffer.BlockCopy(buffer, prefixSize, message, 0, messageLength);
+168
View File
@@ -877,6 +877,174 @@ public enum DisconnectReason
}
```
## Key Security Enhancements
### 1. New Security Validation Layer (SecurityValidator.cs)
**Location:** `EonaCat.Connections/Helpers/SecurityValidator.cs`
**Features:**
- **Length Prefix Validation** - Prevents buffer overflow and memory exhaustion by validating message lengths against configured maximum
- **Message Stream Size Validation** - Detects decompression bombs and message accumulation attacks
- **UDP Packet Validation** - Ensures UDP packets don't exceed buffer boundaries
- **Rate Limiting** - Implements token-bucket style rate limiting per source address to prevent DoS attacks
- **HTTP Header Validation** - Detects header injection attacks by checking for newline and null byte characters
- **Delimiter Validation** - Prevents delimiter injection attacks
- **Compression Ratio Validation** - Detects decompression bomb attacks
- **Error Message Sanitization** - Prevents information leakage in error responses
- **Rate Limit Cache Cleanup** - Prevents memory leaks from rate limit tracking
### 2. Configuration Security Settings
```csharp
public long MaxMessageStreamSize { get; set; } = 500 * 1024 * 1024; // 500 MB
public int MaxMessagesPerSecond { get; set; } = 0; // 0 = unlimited
public bool EnableLengthPrefixValidation { get; set; } = true;
public int MaxUdpPacketsPerSecond { get; set; } = 0; // 0 = unlimited
public int MaxHealthApiRequestSize { get; set; } = 10 * 1024 * 1024; // 10 MB
public bool EnableErrorMessageSanitization { get; set; } = true;
public bool EnableRateLimitCacheCleanup { get; set; } = true;
public int RateLimitCacheCleanupIntervalSeconds { get; set; } = 300;
public bool EnableHttpHeaderValidation { get; set; } = true;
```
**Benefits:**
- Configurable security thresholds for different deployment scenarios
- Ability to fine-tune security vs. performance trade-offs
- Granular control over which protections are enabled
### 3. NetworkServer Hardening
1. **Message Extraction Validation**
- Updated `TryExtractLengthPrefixedMessage()` to use SecurityValidator for length prefix validation
- Added checks to prevent message stream from exceeding configured maximum size
- Enhanced error messages to indicate validation failures
2. **Message Stream Protection**
- `HandleClientCommunicationAsync()` now validates message stream size before writing received data
- Prevents accumulation of large amounts of buffered data that could exhaust memory
- Disconnects clients that exceed limits with proper error logging
3. **UDP Rate Limiting**
- `HandleUdpDataAsync()` implements packet size validation
- UDP packet rate limiting prevents flood attacks
- Per-source tracking of packet rates
4. **Background Cleanup Task**
- Added `CleanupRateLimitCacheAsync()` to periodically clean up rate limit entries
- Prevents memory leaks from unbounded cache growth
- Configurable cleanup interval
5. **Startup Integration**
- Rate limit cache cleanup task integrated into server startup
- Runs as background task with configurable interval
### 4. NetworkClient Hardening
1. **Length-Prefixed Message Validation**
- Updated `ReceiveLengthPrefixedMessagesAsync()` to validate length prefixes using SecurityValidator
- Added dedicated exception handling for malformed length messages
- Improved error messages for validation failures
2. **Delimiter-Based Message Protection**
- Enhanced `ReceiveDelimiterAsync()` with max message stream size enforcement
- Added checks for accumulated message buffer size
- Prevents memory exhaustion from large buffered data
### 5. HealthApiServer REST API Hardening
1. **Request Size Limits**
- New `MaxRequestSize` property to enforce maximum HTTP request/response size
- Defaults to 10 MB, configurable before server starts
- Request line limited to 8192 bytes
- Total headers limited to 65536 bytes
2. **HTTP Header Security**
- Enhanced `ReadRequestLineAsync()` to validate for invalid control characters
- Rejects null bytes (0x00) in request lines
- `DrainHeadersAsync()` validates header size and detects null bytes
- Prevents header-based injection attacks
3. **Response Security Hardening**
- `WriteResponseAsync()` validates response body size against limit
- Content-Type validation prevents injection of control characters
- Additional security headers added:
- `X-Frame-Options: DENY` - Clickjacking protection
- `X-XSS-Protection: 1; mode=block` - XSS protection
- `Strict-Transport-Security` - Forces HTTPS
- `Pragma: no-cache` - Additional cache control
- Improved error handling for oversized responses
## Attack Vectors Mitigated
### 1. Buffer Overflow Attacks
- **Mitigation:** Length prefix validation, buffer size checks, message size limits
- **Implementation:** SecurityValidator validates all length values before buffer allocation
### 2. Memory Exhaustion / Decompression Bombs
- **Mitigation:** MaxMessageStreamSize configuration, per-message size limits
- **Implementation:** Accumulated stream size checked before each write operation
### 3. Slow Loris / Slowhttptest Attacks
- **Mitigation:** Request timeouts, header size limits
- **Implementation:** Network timeouts and maximum header sizes enforced
### 4. UDP Flood / Amplification Attacks
- **Mitigation:** UDP rate limiting, packet size validation
- **Implementation:** Per-source rate limiting with configurable thresholds
### 5. HTTP Header Injection
- **Mitigation:** HTTP header validation, null byte detection
- **Implementation:** SecurityValidator checks for newlines and null bytes in headers
### 6. Malformed Packet Attacks
- **Mitigation:** Strict validation of message framing and length prefixes
- **Implementation:** Enhanced error handling with specific exceptions for malformed data
### 7. Information Leakage
- **Mitigation:** Error message sanitization
- **Implementation:** Sensitive error details hidden from clients, detailed logs for server admins
### 8. Rate Limiting / DoS
- **Mitigation:** Per-client/source rate limiting capability
- **Implementation:** SecurityValidator.CheckRateLimit with configurable thresholds
## Configuration Best Practices
### Production Deployment
```csharp
var config = new Configuration
{
// Message constraints
MAX_MESSAGE_SIZE = 100 * 1024 * 1024, // 100 MB
MaxMessageStreamSize = 500 * 1024 * 1024, // 500 MB
// Rate limiting
MaxMessagesPerSecond = 1000, // Per client
MaxUdpPacketsPerSecond = 5000, // Per source
MaxHealthApiRequestSize = 10 * 1024 * 1024, // 10 MB
// Security features
EnableLengthPrefixValidation = true,
EnableErrorMessageSanitization = true,
EnableRateLimitCacheCleanup = true,
EnableHttpHeaderValidation = true
};
```
### High-Security Deployment
```csharp
var config = new Configuration
{
MAX_MESSAGE_SIZE = 10 * 1024 * 1024, // 10 MB
MaxMessageStreamSize = 50 * 1024 * 1024, // 50 MB
MaxMessagesPerSecond = 100, // Strict rate limit
MaxUdpPacketsPerSecond = 500, // Strict UDP limit
MaxHealthApiRequestSize = 1 * 1024 * 1024, // 1 MB
// All security features enabled by default
};
```
## Performance Considerations
### Optimization Tips