From 2c5b69bf2cb721cc9d7ef8df67b93403cbf1b1d2 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Wed, 24 Jun 2026 06:47:13 +0200 Subject: [PATCH] Added more security --- .../Helpers/HealthApiServer.cs | 71 ++++- .../Helpers/SecurityValidator.cs | 253 ++++++++++++++++++ EonaCat.Connections/Models/Configuration.cs | 50 ++++ EonaCat.Connections/NetworkClient.cs | 29 +- EonaCat.Connections/NetworkServer.cs | 79 +++++- README.md | 168 ++++++++++++ 6 files changed, 638 insertions(+), 12 deletions(-) create mode 100644 EonaCat.Connections/Helpers/SecurityValidator.cs diff --git a/EonaCat.Connections/Helpers/HealthApiServer.cs b/EonaCat.Connections/Helpers/HealthApiServer.cs index a0f015b..f8b5cfb 100644 --- a/EonaCat.Connections/Helpers/HealthApiServer.cs +++ b/EonaCat.Connections/Helpers/HealthApiServer.cs @@ -19,6 +19,17 @@ namespace EonaCat.Connections.Helpers private volatile bool _running; private readonly Func _getHealthJson; private readonly Func _getStatusJson; + private int _maxRequestSize = 10 * 1024 * 1024; // 10 MB default + + /// + /// Gets or sets the maximum HTTP request size in bytes. + /// Defaults to 10 MB. Set before calling Start(). + /// + public int MaxRequestSize + { + get => _maxRequestSize; + set => _maxRequestSize = Math.Max(1024, value); // Minimum 1 KB + } /// /// Gets the actual port the server is listening on. @@ -202,13 +213,14 @@ namespace EonaCat.Connections.Helpers } } - private static async Task ReadRequestLineAsync(NetworkStream stream) + private async Task 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); diff --git a/EonaCat.Connections/Helpers/SecurityValidator.cs b/EonaCat.Connections/Helpers/SecurityValidator.cs new file mode 100644 index 0000000..5247570 --- /dev/null +++ b/EonaCat.Connections/Helpers/SecurityValidator.cs @@ -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. + + /// + /// Provides security validation utilities to protect against common network attacks + /// including buffer overflows, memory exhaustion (decompression bombs), malformed packets, + /// and injection attacks. + /// + public static class SecurityValidator + { + private static readonly ConcurrentDictionary _rateLimitCache = + new ConcurrentDictionary(); + + private class RateLimitEntry + { + public long RequestCount; + public DateTime WindowStart; + } + + /// + /// 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) + /// + 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; + } + + /// + /// Validates message stream size to prevent memory exhaustion attacks. + /// Checks if accumulated message data exceeds configured thresholds. + /// + public static bool ValidateMessageStreamSize(long currentStreamLength, long maxStreamSize) + { + if (currentStreamLength <= 0 || maxStreamSize <= 0) + { + return true; // No limit configured + } + + return currentStreamLength < maxStreamSize; + } + + /// + /// Validates UDP packet size to prevent buffer overflow attacks. + /// + public static bool ValidateUdpPacketSize(int receivedBytes, int bufferSize) + { + // UDP packet should not exceed buffer size + if (receivedBytes <= 0 || receivedBytes > bufferSize) + { + return false; + } + + return true; + } + + /// + /// Implements basic rate limiting per source IP address. + /// Returns true if request is within rate limit, false if limit exceeded. + /// + 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; + } + + /// + /// Validates HTTP header names and values to prevent header injection attacks. + /// + 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; + } + + /// + /// Validates that a message doesn't contain null bytes which could be used for injection attacks. + /// + 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; + } + + /// + /// Validates delimiter to prevent delimiter injection attacks. + /// + 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; + } + + /// + /// Detects potential decompression bomb by checking if uncompressed size ratio is too high. + /// + 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; + } + + /// + /// Cleans up old rate limit entries to prevent memory leaks. + /// Should be called periodically from cleanup/maintenance tasks. + /// + public static void CleanupRateLimitCache(int maxAgeSeconds = 300) + { + var now = DateTime.UtcNow; + var keysToRemove = new System.Collections.Generic.List(); + + 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 _); + } + } + + /// + /// Sanitizes error messages to prevent information leakage. + /// + 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"; + } + } +} diff --git a/EonaCat.Connections/Models/Configuration.cs b/EonaCat.Connections/Models/Configuration.cs index 1e8b2e8..1ca68ac 100644 --- a/EonaCat.Connections/Models/Configuration.cs +++ b/EonaCat.Connections/Models/Configuration.cs @@ -159,6 +159,56 @@ namespace EonaCat.Connections.Models /// public double ReadTimeoutSeconds { get; set; } = 300; + // Security Settings (Protection against network attacks) + + /// + /// 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) + /// + public long MaxMessageStreamSize { get; set; } = 500 * 1024 * 1024; + + /// + /// Maximum number of messages allowed per client per second. 0 means unlimited. (default: 0) + /// Helps prevent flood/DoS attacks. + /// + public int MaxMessagesPerSecond { get; set; } = 0; + + /// + /// Enable strict validation of length-prefixed messages to detect malformed packets. (default: true) + /// + public bool EnableLengthPrefixValidation { get; set; } = true; + + /// + /// Enable rate limiting for UDP packets. 0 means unlimited. (default: 0) + /// + public int MaxUdpPacketsPerSecond { get; set; } = 0; + + /// + /// Maximum HTTP request/response size for health API in bytes. (default: 10 MB) + /// + public int MaxHealthApiRequestSize { get; set; } = 10 * 1024 * 1024; + + /// + /// Enable sanitization of error messages to prevent information leakage. (default: true) + /// When enabled, error details are hidden from clients. + /// + public bool EnableErrorMessageSanitization { get; set; } = true; + + /// + /// Enable automatic cleanup of rate limit cache. (default: true) + /// + public bool EnableRateLimitCacheCleanup { get; set; } = true; + + /// + /// Interval in seconds for cleaning up rate limit cache. (default: 300) + /// + public int RateLimitCacheCleanupIntervalSeconds { get; set; } = 300; + + /// + /// Enable validation of HTTP headers to prevent header injection attacks. (default: true) + /// + public bool EnableHttpHeaderValidation { get; set; } = true; + internal RemoteCertificateValidationCallback GetRemoteCertificateValidationCallback() { return CertificateValidation; diff --git a/EonaCat.Connections/NetworkClient.cs b/EonaCat.Connections/NetworkClient.cs index 700a08f..8ce0a7c 100644 --- a/EonaCat.Connections/NetworkClient.cs +++ b/EonaCat.Connections/NetworkClient.cs @@ -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.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}"); diff --git a/EonaCat.Connections/NetworkServer.cs b/EonaCat.Connections/NetworkServer.cs index e524490..0072fcc 100644 --- a/EonaCat.Connections/NetworkServer.cs +++ b/EonaCat.Connections/NetworkServer.cs @@ -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); diff --git a/README.md b/README.md index 750ab62..6db8ba7 100644 --- a/README.md +++ b/README.md @@ -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