From 6972c2bb3212525c3adb4d1418670ee1705e9c0a Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Mon, 28 Aug 2023 05:08:52 +0200 Subject: [PATCH] Improved malware scan. Added server and user purge --- .../App/MalwareScans/FakePlayerPluginScan.cs | 15 +- Moonlight/App/MalwareScans/MinerJarScan.cs | 15 +- Moonlight/App/MalwareScans/MinerScan.cs | 35 +++++ Moonlight/App/MalwareScans/SelfBotCodeScan.cs | 15 +- Moonlight/App/MalwareScans/SelfBotScan.cs | 15 +- Moonlight/App/Models/Misc/MalwareScan.cs | 2 +- .../MalwareBackgroundScanService.cs | 10 +- Moonlight/App/Services/MalwareScanService.cs | 21 ++- Moonlight/App/Services/ServerService.cs | 10 +- .../Shared/Views/Admin/Security/Malware.razor | 130 +++++++++++++----- Moonlight/Shared/Views/Server/Index.razor | 2 +- 11 files changed, 176 insertions(+), 94 deletions(-) create mode 100644 Moonlight/App/MalwareScans/MinerScan.cs diff --git a/Moonlight/App/MalwareScans/FakePlayerPluginScan.cs b/Moonlight/App/MalwareScans/FakePlayerPluginScan.cs index 926c281f..8c235fe6 100644 --- a/Moonlight/App/MalwareScans/FakePlayerPluginScan.cs +++ b/Moonlight/App/MalwareScans/FakePlayerPluginScan.cs @@ -9,7 +9,7 @@ public class FakePlayerPluginScan : MalwareScan public override string Name => "Fake player plugin scan"; public override string Description => "This scan is a simple fake player plugin scan provided by moonlight"; - public override async Task Scan(Server server, IServiceProvider serviceProvider) + public override async Task Scan(Server server, IServiceProvider serviceProvider) { var serverService = serviceProvider.GetRequiredService(); var access = await serverService.CreateFileAccess(server, null!); @@ -24,19 +24,16 @@ public class FakePlayerPluginScan : MalwareScan { if (fileElement.Name.ToLower().Contains("fakeplayer")) { - return new[] + return new() { - new MalwareScanResult - { - Title = "Fake player plugin", - Description = $"Suspicious plugin file: {fileElement.Name}", - Author = "Marcel Baumgartner" - } + Title = "Fake player plugin", + Description = $"Suspicious plugin file: {fileElement.Name}", + Author = "Marcel Baumgartner" }; } } } - return Array.Empty(); + return null; } } \ No newline at end of file diff --git a/Moonlight/App/MalwareScans/MinerJarScan.cs b/Moonlight/App/MalwareScans/MinerJarScan.cs index ea8ef3b0..a11ccd4c 100644 --- a/Moonlight/App/MalwareScans/MinerJarScan.cs +++ b/Moonlight/App/MalwareScans/MinerJarScan.cs @@ -9,7 +9,7 @@ public class MinerJarScan : MalwareScan public override string Name => "Miner jar scan"; public override string Description => "This scan is a simple miner jar scan provided by moonlight"; - public override async Task Scan(Server server, IServiceProvider serviceProvider) + public override async Task Scan(Server server, IServiceProvider serviceProvider) { var serverService = serviceProvider.GetRequiredService(); var access = await serverService.CreateFileAccess(server, null!); @@ -23,18 +23,15 @@ public class MinerJarScan : MalwareScan if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile)) { - return new[] + return new() { - new MalwareScanResult - { - Title = "Found Miner", - Description = "Detected suspicious library directory which may contain a script for miners", - Author = "Marcel Baumgartner" - } + Title = "Found Miner", + Description = "Detected suspicious library directory which may contain a script for miners", + Author = "Marcel Baumgartner" }; } } - return Array.Empty(); + return null; } } \ No newline at end of file diff --git a/Moonlight/App/MalwareScans/MinerScan.cs b/Moonlight/App/MalwareScans/MinerScan.cs new file mode 100644 index 00000000..fb3e67ef --- /dev/null +++ b/Moonlight/App/MalwareScans/MinerScan.cs @@ -0,0 +1,35 @@ +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Misc; +using Moonlight.App.Services; + +namespace Moonlight.App.MalwareScans; + +public class MinerScan : MalwareScan +{ + public override string Name => "Miner (NEZHA)"; + public override string Description => "Probably a miner"; + public override async Task Scan(Server server, IServiceProvider serviceProvider) + { + var serverService = serviceProvider.GetRequiredService(); + + var access = await serverService.CreateFileAccess(server, null!); + var files = await access.Ls(); + + foreach (var file in files.Where(x => x.IsFile && x.Name.EndsWith(".sh"))) + { + var content = await access.Read(file); + + if (content.ToLower().Contains("nezha")) + { + return new() + { + Title = "Miner", + Description = "Miner start script (NEZHA)", + Author = "Marcel Baumgartner" + }; + } + } + + return null; + } +} \ No newline at end of file diff --git a/Moonlight/App/MalwareScans/SelfBotCodeScan.cs b/Moonlight/App/MalwareScans/SelfBotCodeScan.cs index 3c0a7701..77b6a6a6 100644 --- a/Moonlight/App/MalwareScans/SelfBotCodeScan.cs +++ b/Moonlight/App/MalwareScans/SelfBotCodeScan.cs @@ -9,7 +9,7 @@ public class SelfBotCodeScan : MalwareScan public override string Name => "Selfbot code scan"; public override string Description => "This scan is a simple selfbot code scan provided by moonlight"; - public override async Task Scan(Server server, IServiceProvider serviceProvider) + public override async Task Scan(Server server, IServiceProvider serviceProvider) { var serverService = serviceProvider.GetRequiredService(); var access = await serverService.CreateFileAccess(server, null!); @@ -21,18 +21,15 @@ public class SelfBotCodeScan : MalwareScan if (rawScript.Contains("https://discord.com/api") && !rawScript.Contains("https://discord.com/api/oauth2") && !rawScript.Contains("https://discord.com/api/webhook") || rawScript.Contains("https://rblxwild.com")) //TODO: Export to plugins, add regex for checking { - return new[] + return new MalwareScanResult { - new MalwareScanResult - { - Title = "Potential selfbot", - Description = $"Suspicious script file: {script.Name}", - Author = "Marcel Baumgartner" - } + Title = "Potential selfbot", + Description = $"Suspicious script file: {script.Name}", + Author = "Marcel Baumgartner" }; } } - return Array.Empty(); + return null; } } \ No newline at end of file diff --git a/Moonlight/App/MalwareScans/SelfBotScan.cs b/Moonlight/App/MalwareScans/SelfBotScan.cs index 963017e0..b015b1ba 100644 --- a/Moonlight/App/MalwareScans/SelfBotScan.cs +++ b/Moonlight/App/MalwareScans/SelfBotScan.cs @@ -9,7 +9,7 @@ public class SelfBotScan : MalwareScan public override string Name => "Selfbot Scan"; public override string Description => "This scan is a simple selfbot scan provided by moonlight"; - public override async Task Scan(Server server, IServiceProvider serviceProvider) + public override async Task Scan(Server server, IServiceProvider serviceProvider) { var serverService = serviceProvider.GetRequiredService(); var access = await serverService.CreateFileAccess(server, null!); @@ -17,17 +17,14 @@ public class SelfBotScan : MalwareScan if (fileElements.Any(x => x.Name == "tokens.txt")) { - return new[] + return new MalwareScanResult { - new MalwareScanResult - { - Title = "Found SelfBot", - Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot", - Author = "Marcel Baumgartner" - } + Title = "Found SelfBot", + Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot", + Author = "Marcel Baumgartner" }; } - return Array.Empty(); + return null; } } \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/MalwareScan.cs b/Moonlight/App/Models/Misc/MalwareScan.cs index 9ca18659..9660868c 100644 --- a/Moonlight/App/Models/Misc/MalwareScan.cs +++ b/Moonlight/App/Models/Misc/MalwareScan.cs @@ -6,5 +6,5 @@ public abstract class MalwareScan { public abstract string Name { get; } public abstract string Description { get; } - public abstract Task Scan(Server server, IServiceProvider serviceProvider); + public abstract Task Scan(Server server, IServiceProvider serviceProvider); } \ No newline at end of file diff --git a/Moonlight/App/Services/Background/MalwareBackgroundScanService.cs b/Moonlight/App/Services/Background/MalwareBackgroundScanService.cs index dcf975a5..282fa275 100644 --- a/Moonlight/App/Services/Background/MalwareBackgroundScanService.cs +++ b/Moonlight/App/Services/Background/MalwareBackgroundScanService.cs @@ -15,7 +15,7 @@ public class MalwareBackgroundScanService public bool IsRunning => !ScanTask?.IsCompleted ?? false; public bool ScanAllServers { get; set; } - public readonly Dictionary ScanResults; + public readonly Dictionary ScanResults; public string Status { get; private set; } = "N/A"; private Task? ScanTask; @@ -70,16 +70,16 @@ public class MalwareBackgroundScanService Status = $"[{i} / {servers.Length}] Scanning server {server.Name}"; await Event.Emit("malwareScan.status", IsRunning); - var results = await malwareScanService.Perform(server); + var result = await malwareScanService.Perform(server); - if (results.Any()) + if (result != null) { lock (ScanResults) { - ScanResults.Add(server, results); + ScanResults.Add(server, result); } - await Event.Emit("malwareScan.result"); + await Event.Emit("malwareScan.result", server); } i++; diff --git a/Moonlight/App/Services/MalwareScanService.cs b/Moonlight/App/Services/MalwareScanService.cs index 08dbaabf..3674cdd0 100644 --- a/Moonlight/App/Services/MalwareScanService.cs +++ b/Moonlight/App/Services/MalwareScanService.cs @@ -5,7 +5,7 @@ using Moonlight.App.Services.Plugins; namespace Moonlight.App.Services; -public class MalwareScanService //TODO: Make this moddable using plugins +public class MalwareScanService { private readonly PluginService PluginService; private readonly IServiceScopeFactory ServiceScopeFactory; @@ -16,34 +16,29 @@ public class MalwareScanService //TODO: Make this moddable using plugins ServiceScopeFactory = serviceScopeFactory; } - public async Task Perform(Server server) + public async Task Perform(Server server) { var defaultScans = new List { new SelfBotScan(), new MinerJarScan(), new SelfBotCodeScan(), - new FakePlayerPluginScan() + new FakePlayerPluginScan(), + new MinerScan() }; var scans = await PluginService.BuildMalwareScans(defaultScans.ToArray()); - - var results = new List(); + using var scope = ServiceScopeFactory.CreateScope(); foreach (var scan in scans) { var result = await scan.Scan(server, scope.ServiceProvider); - if (result.Any()) - { - foreach (var scanResult in result) - { - results.Add(scanResult); - } - } + if (result != null) + return result; } - return results.ToArray(); + return null; } } \ No newline at end of file diff --git a/Moonlight/App/Services/ServerService.cs b/Moonlight/App/Services/ServerService.cs index 7e9876d3..9054e01b 100644 --- a/Moonlight/App/Services/ServerService.cs +++ b/Moonlight/App/Services/ServerService.cs @@ -113,19 +113,17 @@ public class ServerService if (ConfigService.Get().Moonlight.Security.MalwareCheckOnStart && signal == PowerSignal.Start || signal == PowerSignal.Restart) { - var results = await new MalwareScanService( + var result = await new MalwareScanService( PluginService, ServiceScopeFactory ).Perform(server); - if (results.Any()) + if (result != null) { - var resultText = string.Join(" ", results.Select(x => x.Title)); - - Logger.Warn($"Found malware on server {server.Uuid}. Results: " + resultText); + Logger.Warn($"Found malware on server {server.Uuid}. Result: " + result.Title); throw new DisplayException( - $"Unable to start server. Found following malware on this server: {resultText}. Please contact the support if you think this detection is a false positive", + $"Unable to start server. Found following malware on this server: {result.Title}. Please contact the support if you think this detection is a false positive", true); } } diff --git a/Moonlight/Shared/Views/Admin/Security/Malware.razor b/Moonlight/Shared/Views/Admin/Security/Malware.razor index bb4a2a6e..d3f682f5 100644 --- a/Moonlight/Shared/Views/Admin/Security/Malware.razor +++ b/Moonlight/Shared/Views/Admin/Security/Malware.razor @@ -4,12 +4,23 @@ @using Moonlight.App.Services.Background @using Moonlight.App.Services @using BlazorTable +@using Microsoft.EntityFrameworkCore +@using Moonlight.App.ApiClients.Wings @using Moonlight.App.Database.Entities @using Moonlight.App.Events +@using Moonlight.App.Helpers @using Moonlight.App.Models.Misc +@using Moonlight.App.Repositories +@using Moonlight.App.Services.Interop +@using Moonlight.App.Services.Sessions @inject MalwareBackgroundScanService MalwareBackgroundScanService @inject SmartTranslateService SmartTranslateService +@inject ServerService ServerService +@inject ToastService ToastService +@inject SessionServerService SessionServerService +@inject Repository ServerRepository +@inject Repository UserRepository @inject EventSystem Event @implements IDisposable @@ -38,7 +49,7 @@ } else { -
+
+ + + }
@@ -65,39 +81,14 @@
- +
- - - + +
@@ -109,7 +100,9 @@ @code { - private readonly Dictionary ScanResults = new(); + private readonly Dictionary ScanResults = new(); + + private Table Table; private LazyLoader LazyLoaderResults; @@ -117,7 +110,15 @@ { await Event.On("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); }); - await Event.On("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); }); + await Event.On("malwareScan.result", this, async server => + { + lock (MalwareBackgroundScanService.ScanResults) + { + ScanResults.Add(server, MalwareBackgroundScanService.ScanResults[server]); + } + + await InvokeAsync(StateHasChanged); + }); } private Task LoadResults(LazyLoader arg) @@ -140,4 +141,69 @@ await Event.Off("malwareScan.status", this); await Event.Off("malwareScan.result", this); } + + private async Task PurgeSelected() + { + int users = 0; + int servers = 0; + + int allServersCount = Table.FilteredItems.Count(); + int position = 0; + + await ToastService.CreateProcessToast("purgeProcess", "Purging"); + + foreach (var item in Table.FilteredItems) + { + position++; + + if (item == null) + continue; + + try + { + var server = ServerRepository.Get() + .Include(x => x.Owner) + .FirstOrDefault(x => x.Id == item.Id); + + if(server == null) + continue; + + await ToastService.UpdateProcessToast("purgeProcess", $"[{position}/{allServersCount}] {server.Name}"); + + ScanResults.Remove(item); + + await InvokeAsync(StateHasChanged); + + // Owner + + server.Owner.Status = UserStatus.Banned; + UserRepository.Update(server.Owner); + users++; + + try + { + await SessionServerService.ReloadUserSessions(server.Owner); + } + catch (Exception) {/* Ignored */} + + // Server itself + + await ServerService.SetPowerState(server, PowerSignal.Kill); + await ServerService.Delete(server); + servers++; + } + catch (Exception e) + { + Logger.Warn($"Error purging server: {item.Uuid}"); + Logger.Warn(e); + + await ToastService.Error( + $"Failed to purge server '{item.Name}': {e.Message}" + ); + } + } + + await ToastService.RemoveProcessToast("purgeProcess"); + await ToastService.Success($"Successfully purged {servers} servers by {users} users"); + } } \ No newline at end of file diff --git a/Moonlight/Shared/Views/Server/Index.razor b/Moonlight/Shared/Views/Server/Index.razor index 39e50af0..61da5850 100644 --- a/Moonlight/Shared/Views/Server/Index.razor +++ b/Moonlight/Shared/Views/Server/Index.razor @@ -190,7 +190,7 @@ .Include(x => x.Variables) .Include(x => x.MainAllocation) .Include(x => x.Owner) - .First(x => x.Uuid == uuid); + .FirstOrDefault(x => x.Uuid == uuid); if (CurrentServer != null) {