From d2dabddeb794ae2c1394e964099108701853007d Mon Sep 17 00:00:00 2001 From: azyges <229218900+azyges@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:41:56 +0900 Subject: [PATCH] validate incoming cid's --- .../Configuration/BroadcastConfiguration.cs | 2 +- .../Configuration/BroadcastOptions.cs | 2 +- .../Hubs/LightlessHub.Groups.cs | 7 +-- .../Hubs/LightlessHub.User.cs | 51 +++++++++++++++++++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs index 5b796aa..560ea00 100644 --- a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs +++ b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastConfiguration.cs @@ -5,7 +5,7 @@ namespace LightlessSyncServer.Configuration; public class BroadcastConfiguration : IBroadcastConfiguration { - private static readonly TimeSpan DefaultEntryTtl = TimeSpan.FromMinutes(5); + private static readonly TimeSpan DefaultEntryTtl = TimeSpan.FromMinutes(180); private const int DefaultMaxStatusBatchSize = 30; private const string DefaultNotificationTemplate = "{DisplayName} sent you a pair request. To accept, right-click them, open the context menu, and send a request back."; diff --git a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs index 960c8ca..858255a 100644 --- a/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Configuration/BroadcastOptions.cs @@ -8,7 +8,7 @@ public class BroadcastOptions public string RedisKeyPrefix { get; set; } = "broadcast:"; [Range(1, int.MaxValue)] - public int EntryTtlSeconds { get; set; } = 300; + public int EntryTtlSeconds { get; set; } = 10800; [Range(1, int.MaxValue)] public int MaxStatusBatchSize { get; set; } = 30; diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs index 1204590..69a32e4 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs @@ -371,11 +371,6 @@ public partial class LightlessHub return new GroupJoinInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.ToEnum(), true); } - private static bool IsHex(char c) => - (c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || - (c >= 'A' && c <= 'F'); - [Authorize(Policy = "Identified")] public async Task GroupJoinFinalize(GroupJoinDto dto) { @@ -386,7 +381,7 @@ public partial class LightlessHub var group = await DbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == aliasOrGid || g.Alias == aliasOrGid, cancellationToken: _contextAccessor.HttpContext.RequestAborted).ConfigureAwait(false); var groupGid = group?.GID ?? string.Empty; var existingPair = await DbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.GroupUserUID == UserUID).ConfigureAwait(false); - var isHashedPassword = dto.Password.Length == 64 && dto.Password.All(IsHex); + var isHashedPassword = dto.Password.Length == 64 && dto.Password.All(Uri.IsHexDigit); var hashedPw = isHashedPassword ? dto.Password : StringUtils.Sha256String(dto.Password); diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs index 3248a00..914e181 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs @@ -148,6 +148,13 @@ public partial class LightlessHub if (string.IsNullOrWhiteSpace(otherCid) || string.IsNullOrWhiteSpace(myCid)) return; + bool IsValidCid(string cid) => cid.Length == 64 && cid.All(Uri.IsHexDigit) && !cid.All(c => c == '0'); + + if (!IsValidCid(myCid) || !IsValidCid(otherCid)) + { + return; + } + if (string.Equals(otherCid, myCid, StringComparison.Ordinal)) { await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "You can't pair with yourself.").ConfigureAwait(false); @@ -338,6 +345,18 @@ public partial class LightlessHub return; } + if (string.IsNullOrWhiteSpace(hashedCid) || hashedCid.Length != 64 || !hashedCid.All(c => Uri.IsHexDigit(c))) + { + _logger.LogCallWarning(LightlessHubLogger.Args("invalid cid format", UserUID, "CID", hashedCid)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Invalid CID format.").ConfigureAwait(false); + return; + } + + if (hashedCid.All(c => c == '0')) + { + return; + } + var db = _redis.Database; var broadcastKey = _broadcastConfiguration.BuildRedisKey(hashedCid); @@ -438,6 +457,18 @@ public partial class LightlessHub if (!_broadcastConfiguration.EnableBroadcasting) return null; + if (string.IsNullOrWhiteSpace(hashedCid) || hashedCid.Length != 64 || !hashedCid.All(c => Uri.IsHexDigit(c))) + { + _logger.LogCallWarning(LightlessHubLogger.Args("invalid cid format", UserUID, "CID", hashedCid)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Invalid CID format.").ConfigureAwait(false); + return null; + } + + if (hashedCid.All(c => c == '0')) + { + return null; + } + var db = _redis.Database; var key = _broadcastConfiguration.BuildRedisKey(hashedCid); @@ -479,6 +510,18 @@ public partial class LightlessHub if (!_broadcastConfiguration.EnableBroadcasting) return null; + if (string.IsNullOrWhiteSpace(hashedCid) || hashedCid.Length != 64 || !hashedCid.All(c => Uri.IsHexDigit(c))) + { + _logger.LogCallWarning(LightlessHubLogger.Args("invalid cid format", UserUID, "CID", hashedCid)); + await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Invalid CID format.").ConfigureAwait(false); + return null; + } + + if (hashedCid.All(c => c == '0')) + { + return null; + } + var db = _redis.Database; var key = _broadcastConfiguration.BuildRedisKey(hashedCid); @@ -542,6 +585,14 @@ public partial class LightlessHub var tasks = new Dictionary>(hashedCids.Count); foreach (var cid in hashedCids) { + bool validHash = !string.IsNullOrWhiteSpace(cid) && cid.Length == 64 && cid.All(Uri.IsHexDigit) && !cid.All(c => c == '0'); + + if (!validHash) + { + tasks[cid] = Task.FromResult(new RedisValueWithExpiry(RedisValue.Null, null)); + continue; + } + var key = _broadcastConfiguration.BuildRedisKey(cid); tasks[cid] = db.StringGetWithExpiryAsync(key); }