CVE-2021-32795: Improper Input Validation (CWE-20)
value: ArchiSteamFarm is a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. In versions prior to 4.3.1.0 a Denial of Service (aka DoS) vulnerability which allows attacker to remotely crash running ASF instance through sending a specifically-crafted Steam chat message exists. The user sending the message does not need to be authorized within the bot or ASF process. The attacker needs to know ASF's `CommandPrefix` in advance, but majority of ASF setups run with an unchanged default value. This attack does not allow attacker to gain any potentially-sensitive information, such as logins or passwords, does not allow to execute arbitrary commands and otherwise exploit the crash further. The issue is patched in ASF V4.3.1.0. The only workaround which guarantees complete protection is running all bots with `OnlineStatus` of `0` (Offline). In this setup, ASF is able to ignore even the specifically-crafted message without attempting to interpret it.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 5.9
Published: 2021-07-26T20:15Z
Repository: https://github.com/JustArchiNET/ArchiSteamFarm
Language: C#
Files changed: ArchiSteamFarm/Bot.cs, ArchiSteamFarm/Commands.cs
Commit date: 2020-10-14 12:06:42 +0200
Commit message: Avoid crash on commandPrefix message + hardening

Diff: diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs
index 6dc16f6e..733e0f45 100755
--- a/ArchiSteamFarm/Bot.cs
+++ b/ArchiSteamFarm/Bot.cs
@@ -2239,10 +2239,16 @@ public sealed class Bot : IAsyncDisposable {
 		private async Task OnIncomingChatMessage(CChatRoom_IncomingChatMessage_Notification notification) {
 			if (notification == null) {
 				throw new ArgumentNullException(nameof(notification));
 			}
 
+			if ((notification.chat_group_id == 0) || (notification.chat_id == 0) || (notification.steamid_sender == 0)) {
+				ArchiLogger.LogNullError(nameof(notification.chat_group_id) + " || " + nameof(notification.chat_id) + " || " + nameof(notification.steamid_sender));
+
+				return;
+			}
+
 			// Under normal circumstances, timestamp must always be greater than 0, but Steam already proved that it's capable of going against the logic
 			if ((notification.steamid_sender != SteamID) && (notification.timestamp > 0)) {
 				if (ShouldAckChatMessage(notification.steamid_sender)) {
 					Utilities.InBackground(() => ArchiHandler.AckChatMessage(notification.chat_group_id, notification.chat_id, notification.timestamp));
 				}
@@ -2275,10 +2281,16 @@ public sealed class Bot : IAsyncDisposable {
 		private async Task OnIncomingMessage(CFriendMessages_IncomingMessage_Notification notification) {
 			if (notification == null) {
 				throw new ArgumentNullException(nameof(notification));
 			}
 
+			if (notification.steamid_friend == 0) {
+				ArchiLogger.LogNullError(nameof(notification.steamid_friend));
+
+				return;
+			}
+
 			if ((EChatEntryType) notification.chat_entry_type != EChatEntryType.ChatMsg) {
 				return;
 			}
 
 			// Under normal circumstances, timestamp must always be greater than 0, but Steam already proved that it's capable of going against the logic
diff --git a/ArchiSteamFarm/Commands.cs b/ArchiSteamFarm/Commands.cs
index 99058eea..9451a3ba 100644
--- a/ArchiSteamFarm/Commands.cs
+++ b/ArchiSteamFarm/Commands.cs
@@ -305,11 +305,16 @@ public sealed class Commands {
 					}
 
 					return;
 				}
 
-				message = message.Substring(commandPrefix!.Length);
+				if (message.Length == commandPrefix!.Length) {
+					// If the message starts with command prefix and is of the same length as command prefix, then it's just empty command trigger, useless
+					return;
+				}
+
+				message = message.Substring(commandPrefix.Length);
 			}
 
 			Task<string?> responseTask = Response(steamID, message);
 
 			bool feedback = Bot.HasPermission(steamID, BotConfig.EPermission.FamilySharing);
@@ -362,11 +367,16 @@ public sealed class Commands {
 					}
 
 					return;
 				}
 
-				message = message.Substring(commandPrefix!.Length);
+				if (message.Length == commandPrefix!.Length) {
+					// If the message starts with command prefix and is of the same length as command prefix, then it's just empty command trigger, useless
+					return;
+				}
+
+				message = message.Substring(commandPrefix.Length);
 			}
 
 			Task<string?> responseTask = Response(steamID, message);
 
 			bool feedback = Bot.HasPermission(steamID, BotConfig.EPermission.FamilySharing);

Vulnerable code: 				message = message.Substring(commandPrefix!.Length);
				message = message.Substring(commandPrefix!.Length);
Fixed code: 			if ((notification.chat_group_id == 0) || (notification.chat_id == 0) || (notification.steamid_sender == 0)) {
				ArchiLogger.LogNullError(nameof(notification.chat_group_id) + " || " + nameof(notification.chat_id) + " || " + nameof(notification.steamid_sender));

				return;
			}

			if (notification.steamid_friend == 0) {
				ArchiLogger.LogNullError(nameof(notification.steamid_friend));

				return;
			}

				if (message.Length == commandPrefix!.Length) {
					// If the message starts with command prefix and is of the same length as command prefix, then it's just empty command trigger, useless
					return;
				}

				message = message.Substring(commandPrefix.Length);
				if (message.Length == commandPrefix!.Length) {
					// If the message starts with command prefix and is of the same length as command prefix, then it's just empty command trigger, useless
					return;
				}

				message = message.Substring(commandPrefix.Length);