Merge pull request #268 from Moonlight-Panel/AddBetterSelfbotDetection
Improved the whole malware scan system. Made it possible for plugins to add them. Improved self bot detection for python
This commit is contained in:
@@ -290,10 +290,15 @@ public class ConfigV1
|
|||||||
public class SecurityData
|
public class SecurityData
|
||||||
{
|
{
|
||||||
[JsonProperty("Token")]
|
[JsonProperty("Token")]
|
||||||
[Description("This is the moonlight app token. It is used to encrypt and decrypt data and validte tokens and sessions")]
|
[Description("This is the moonlight app token. It is used to encrypt and decrypt data and validate tokens and sessions")]
|
||||||
[Blur]
|
[Blur]
|
||||||
public string Token { get; set; } = Guid.NewGuid().ToString();
|
public string Token { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
[JsonProperty("MalwareCheckOnStart")]
|
||||||
|
[Description(
|
||||||
|
"This option will enable the scanning for malware on every server before it has been started and if something has been found, the power action will be canceled")]
|
||||||
|
public bool MalwareCheckOnStart { get; set; } = true;
|
||||||
|
|
||||||
[JsonProperty("BlockIpDuration")]
|
[JsonProperty("BlockIpDuration")]
|
||||||
[Description("The duration in minutes a ip will be blocked by the anti ddos system")]
|
[Description("The duration in minutes a ip will be blocked by the anti ddos system")]
|
||||||
public int BlockIpDuration { get; set; } = 15;
|
public int BlockIpDuration { get; set; } = 15;
|
||||||
|
|||||||
42
Moonlight/App/MalwareScans/FakePlayerPluginScan.cs
Normal file
42
Moonlight/App/MalwareScans/FakePlayerPluginScan.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.App.MalwareScans;
|
||||||
|
|
||||||
|
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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var serverService = serviceProvider.GetRequiredService<ServerService>();
|
||||||
|
var access = await serverService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => !x.IsFile && x.Name == "plugins")) // Check for plugins folder
|
||||||
|
{
|
||||||
|
await access.Cd("plugins");
|
||||||
|
fileElements = await access.Ls();
|
||||||
|
|
||||||
|
foreach (var fileElement in fileElements)
|
||||||
|
{
|
||||||
|
if (fileElement.Name.ToLower().Contains("fakeplayer"))
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new MalwareScanResult
|
||||||
|
{
|
||||||
|
Title = "Fake player plugin",
|
||||||
|
Description = $"Suspicious plugin file: {fileElement.Name}",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<MalwareScanResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Moonlight/App/MalwareScans/MinerJarScan.cs
Normal file
40
Moonlight/App/MalwareScans/MinerJarScan.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.App.MalwareScans;
|
||||||
|
|
||||||
|
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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var serverService = serviceProvider.GetRequiredService<ServerService>();
|
||||||
|
var access = await serverService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => x.Name == "libraries" && !x.IsFile))
|
||||||
|
{
|
||||||
|
await access.Cd("libraries");
|
||||||
|
|
||||||
|
fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile))
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new MalwareScanResult
|
||||||
|
{
|
||||||
|
Title = "Found Miner",
|
||||||
|
Description = "Detected suspicious library directory which may contain a script for miners",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<MalwareScanResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Moonlight/App/MalwareScans/SelfBotCodeScan.cs
Normal file
38
Moonlight/App/MalwareScans/SelfBotCodeScan.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.App.MalwareScans;
|
||||||
|
|
||||||
|
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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var serverService = serviceProvider.GetRequiredService<ServerService>();
|
||||||
|
var access = await serverService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
foreach (var script in fileElements.Where(x => x.Name.EndsWith(".py") && x.IsFile))
|
||||||
|
{
|
||||||
|
var rawScript = await access.Read(script);
|
||||||
|
|
||||||
|
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[]
|
||||||
|
{
|
||||||
|
new MalwareScanResult
|
||||||
|
{
|
||||||
|
Title = "Potential selfbot",
|
||||||
|
Description = $"Suspicious script file: {script.Name}",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<MalwareScanResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Moonlight/App/MalwareScans/SelfBotScan.cs
Normal file
33
Moonlight/App/MalwareScans/SelfBotScan.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.App.MalwareScans;
|
||||||
|
|
||||||
|
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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var serverService = serviceProvider.GetRequiredService<ServerService>();
|
||||||
|
var access = await serverService.CreateFileAccess(server, null!);
|
||||||
|
var fileElements = await access.Ls();
|
||||||
|
|
||||||
|
if (fileElements.Any(x => x.Name == "tokens.txt"))
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new MalwareScanResult
|
||||||
|
{
|
||||||
|
Title = "Found SelfBot",
|
||||||
|
Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
|
||||||
|
Author = "Marcel Baumgartner"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<MalwareScanResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Moonlight/App/Models/Misc/MalwareScan.cs
Normal file
10
Moonlight/App/Models/Misc/MalwareScan.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public abstract class MalwareScan
|
||||||
|
{
|
||||||
|
public abstract string Name { get; }
|
||||||
|
public abstract string Description { get; }
|
||||||
|
public abstract Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
public class MalwareScanResult
|
public class MalwareScanResult
|
||||||
{
|
{
|
||||||
public string Title { get; set; } = "";
|
public string Title { get; set; }
|
||||||
public string Description { get; set; } = "";
|
public string Description { get; set; } = "";
|
||||||
public string Author { get; set; } = "";
|
public string Author { get; set; } = "";
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Plugin.UI.Servers;
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Plugin.UI.Servers;
|
||||||
using Moonlight.App.Plugin.UI.Webspaces;
|
using Moonlight.App.Plugin.UI.Webspaces;
|
||||||
|
|
||||||
namespace Moonlight.App.Plugin;
|
namespace Moonlight.App.Plugin;
|
||||||
@@ -12,4 +13,5 @@ public abstract class MoonlightPlugin
|
|||||||
public Func<ServerPageContext, Task>? OnBuildServerPage { get; set; }
|
public Func<ServerPageContext, Task>? OnBuildServerPage { get; set; }
|
||||||
public Func<WebspacePageContext, Task>? OnBuildWebspacePage { get; set; }
|
public Func<WebspacePageContext, Task>? OnBuildWebspacePage { get; set; }
|
||||||
public Func<IServiceCollection, Task>? OnBuildServices { get; set; }
|
public Func<IServiceCollection, Task>? OnBuildServices { get; set; }
|
||||||
|
public Func<List<MalwareScan>, Task<List<MalwareScan>>>? OnBuildMalwareScans { get; set; }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using Moonlight.App.ApiClients.Daemon.Resources;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|
||||||
|
public class MalwareBackgroundScanService
|
||||||
|
{
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public bool IsRunning => !ScanTask?.IsCompleted ?? false;
|
||||||
|
public bool ScanAllServers { get; set; }
|
||||||
|
public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
|
||||||
|
public string Status { get; private set; } = "N/A";
|
||||||
|
|
||||||
|
private Task? ScanTask;
|
||||||
|
|
||||||
|
public MalwareBackgroundScanService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
Event = eventSystem;
|
||||||
|
ScanResults = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Start()
|
||||||
|
{
|
||||||
|
if (IsRunning)
|
||||||
|
throw new DisplayException("Malware scan is already running");
|
||||||
|
|
||||||
|
ScanTask = Task.Run(Run);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Run()
|
||||||
|
{
|
||||||
|
// Clean results
|
||||||
|
Status = "Clearing last results";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
lock (ScanResults)
|
||||||
|
ScanResults.Clear();
|
||||||
|
|
||||||
|
await Event.Emit("malwareScan.result");
|
||||||
|
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var serverRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||||
|
var malwareScanService = scope.ServiceProvider.GetRequiredService<MalwareScanService>();
|
||||||
|
|
||||||
|
Status = "Fetching servers to scan";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
Server[] servers;
|
||||||
|
|
||||||
|
if (ScanAllServers)
|
||||||
|
servers = serverRepo.Get().ToArray();
|
||||||
|
else
|
||||||
|
servers = await GetOnlineServers();
|
||||||
|
|
||||||
|
// Perform scan
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
foreach (var server in servers)
|
||||||
|
{
|
||||||
|
Status = $"[{i} / {servers.Length}] Scanning server {server.Name}";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
var results = await malwareScanService.Perform(server);
|
||||||
|
|
||||||
|
if (results.Any())
|
||||||
|
{
|
||||||
|
lock (ScanResults)
|
||||||
|
{
|
||||||
|
ScanResults.Add(server, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Event.Emit("malwareScan.result");
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(async () => // Because we use the task as the status indicator we need to notify the event system in a new task
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Server[]> GetOnlineServers()
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
// Load services from di scope
|
||||||
|
var nodeRepo = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
||||||
|
var serverRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||||
|
var nodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
||||||
|
|
||||||
|
var nodes = nodeRepo.Get().ToArray();
|
||||||
|
var containers = new List<Container>();
|
||||||
|
|
||||||
|
// Fetch and summarize all running containers from all nodes
|
||||||
|
Logger.Verbose("Fetching and summarizing all running containers from all nodes");
|
||||||
|
|
||||||
|
Status = "Fetching and summarizing all running containers from all nodes";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
var metrics = await nodeService.GetDockerMetrics(node);
|
||||||
|
|
||||||
|
foreach (var container in metrics.Containers)
|
||||||
|
{
|
||||||
|
containers.Add(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerServerMapped = new Dictionary<Server, Container>();
|
||||||
|
|
||||||
|
// Map all the containers to their corresponding server if existing
|
||||||
|
Logger.Verbose("Mapping all the containers to their corresponding server if existing");
|
||||||
|
|
||||||
|
Status = "Mapping all the containers to their corresponding server if existing";
|
||||||
|
await Event.Emit("malwareScan.status", IsRunning);
|
||||||
|
|
||||||
|
foreach (var container in containers)
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(container.Name, out Guid uuid))
|
||||||
|
{
|
||||||
|
var server = serverRepo
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Uuid == uuid);
|
||||||
|
|
||||||
|
if(server == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
containerServerMapped.Add(server, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerServerMapped.Keys.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
using Moonlight.App.ApiClients.Daemon.Resources;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Events;
|
|
||||||
using Moonlight.App.Exceptions;
|
|
||||||
using Moonlight.App.Helpers;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Background;
|
|
||||||
|
|
||||||
public class MalwareScanService
|
|
||||||
{
|
|
||||||
private Repository<Server> ServerRepository;
|
|
||||||
private Repository<Node> NodeRepository;
|
|
||||||
private NodeService NodeService;
|
|
||||||
private ServerService ServerService;
|
|
||||||
|
|
||||||
private readonly EventSystem Event;
|
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
|
||||||
|
|
||||||
public bool IsRunning { get; private set; }
|
|
||||||
public bool ScanAllServers { get; set; }
|
|
||||||
public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
|
|
||||||
public string Status { get; private set; } = "N/A";
|
|
||||||
|
|
||||||
public MalwareScanService(IServiceScopeFactory serviceScopeFactory, EventSystem eventSystem)
|
|
||||||
{
|
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
|
||||||
Event = eventSystem;
|
|
||||||
ScanResults = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Start()
|
|
||||||
{
|
|
||||||
if (IsRunning)
|
|
||||||
throw new DisplayException("Malware scan is already running");
|
|
||||||
|
|
||||||
Task.Run(Run);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Run()
|
|
||||||
{
|
|
||||||
// Clean results
|
|
||||||
IsRunning = true;
|
|
||||||
Status = "Clearing last results";
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
|
|
||||||
lock (ScanResults)
|
|
||||||
{
|
|
||||||
ScanResults.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Event.Emit("malwareScan.result");
|
|
||||||
|
|
||||||
// Load servers to scan
|
|
||||||
|
|
||||||
using var scope = ServiceScopeFactory.CreateScope();
|
|
||||||
|
|
||||||
// Load services from di scope
|
|
||||||
NodeRepository = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
|
||||||
ServerRepository = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
|
||||||
NodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
|
||||||
ServerService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
||||||
|
|
||||||
Status = "Fetching servers to scan";
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
|
|
||||||
Server[] servers;
|
|
||||||
|
|
||||||
if (ScanAllServers)
|
|
||||||
servers = ServerRepository.Get().ToArray();
|
|
||||||
else
|
|
||||||
servers = await GetOnlineServers();
|
|
||||||
|
|
||||||
// Perform scan
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
foreach (var server in servers)
|
|
||||||
{
|
|
||||||
Status = $"[{i} / {servers.Length}] Scanning server {server.Name}";
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
|
|
||||||
var results = await PerformScanOnServer(server);
|
|
||||||
|
|
||||||
if (results.Any())
|
|
||||||
{
|
|
||||||
lock (ScanResults)
|
|
||||||
{
|
|
||||||
ScanResults.Add(server, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Event.Emit("malwareScan.result");
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsRunning = false;
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Server[]> GetOnlineServers()
|
|
||||||
{
|
|
||||||
using var scope = ServiceScopeFactory.CreateScope();
|
|
||||||
|
|
||||||
// Load services from di scope
|
|
||||||
NodeRepository = scope.ServiceProvider.GetRequiredService<Repository<Node>>();
|
|
||||||
ServerRepository = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
|
||||||
NodeService = scope.ServiceProvider.GetRequiredService<NodeService>();
|
|
||||||
ServerService = scope.ServiceProvider.GetRequiredService<ServerService>();
|
|
||||||
|
|
||||||
var nodes = NodeRepository.Get().ToArray();
|
|
||||||
var containers = new List<Container>();
|
|
||||||
|
|
||||||
// Fetch and summarize all running containers from all nodes
|
|
||||||
Logger.Verbose("Fetching and summarizing all running containers from all nodes");
|
|
||||||
|
|
||||||
Status = "Fetching and summarizing all running containers from all nodes";
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
|
|
||||||
foreach (var node in nodes)
|
|
||||||
{
|
|
||||||
var metrics = await NodeService.GetDockerMetrics(node);
|
|
||||||
|
|
||||||
foreach (var container in metrics.Containers)
|
|
||||||
{
|
|
||||||
containers.Add(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var containerServerMapped = new Dictionary<Server, Container>();
|
|
||||||
|
|
||||||
// Map all the containers to their corresponding server if existing
|
|
||||||
Logger.Verbose("Mapping all the containers to their corresponding server if existing");
|
|
||||||
|
|
||||||
Status = "Mapping all the containers to their corresponding server if existing";
|
|
||||||
await Event.Emit("malwareScan.status", IsRunning);
|
|
||||||
|
|
||||||
foreach (var container in containers)
|
|
||||||
{
|
|
||||||
if (Guid.TryParse(container.Name, out Guid uuid))
|
|
||||||
{
|
|
||||||
var server = ServerRepository
|
|
||||||
.Get()
|
|
||||||
.FirstOrDefault(x => x.Uuid == uuid);
|
|
||||||
|
|
||||||
if(server == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
containerServerMapped.Add(server, container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerServerMapped.Keys.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<MalwareScanResult[]> PerformScanOnServer(Server server)
|
|
||||||
{
|
|
||||||
var results = new List<MalwareScanResult>();
|
|
||||||
|
|
||||||
// TODO: Move scans to an universal format / api
|
|
||||||
|
|
||||||
// Define scans here
|
|
||||||
|
|
||||||
async Task ScanSelfBot()
|
|
||||||
{
|
|
||||||
var access = await ServerService.CreateFileAccess(server, null!);
|
|
||||||
var fileElements = await access.Ls();
|
|
||||||
|
|
||||||
if (fileElements.Any(x => x.Name == "tokens.txt"))
|
|
||||||
{
|
|
||||||
results.Add(new ()
|
|
||||||
{
|
|
||||||
Title = "Found SelfBot",
|
|
||||||
Description = "Detected suspicious 'tokens.txt' file which may contain tokens for a selfbot",
|
|
||||||
Author = "Marcel Baumgartner"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ScanMinerJar()
|
|
||||||
{
|
|
||||||
var access = await ServerService.CreateFileAccess(server, null!);
|
|
||||||
var fileElements = await access.Ls();
|
|
||||||
|
|
||||||
if (fileElements.Any(x => x.Name == "libraries" && !x.IsFile))
|
|
||||||
{
|
|
||||||
await access.Cd("libraries");
|
|
||||||
|
|
||||||
fileElements = await access.Ls();
|
|
||||||
|
|
||||||
if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile))
|
|
||||||
{
|
|
||||||
results.Add(new ()
|
|
||||||
{
|
|
||||||
Title = "Found Miner",
|
|
||||||
Description = "Detected suspicious library directory which may contain a script for miners",
|
|
||||||
Author = "Marcel Baumgartner"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ScanFakePlayerPlugins()
|
|
||||||
{
|
|
||||||
var access = await ServerService.CreateFileAccess(server, null!);
|
|
||||||
var fileElements = await access.Ls();
|
|
||||||
|
|
||||||
if (fileElements.Any(x => !x.IsFile && x.Name == "plugins")) // Check for plugins folder
|
|
||||||
{
|
|
||||||
await access.Cd("plugins");
|
|
||||||
fileElements = await access.Ls();
|
|
||||||
|
|
||||||
foreach (var fileElement in fileElements)
|
|
||||||
{
|
|
||||||
if (fileElement.Name.ToLower().Contains("fake"))
|
|
||||||
{
|
|
||||||
results.Add(new()
|
|
||||||
{
|
|
||||||
Title = "Fake player plugin",
|
|
||||||
Description = $"Suspicious plugin file: {fileElement.Name}",
|
|
||||||
Author = "Marcel Baumgartner"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute scans
|
|
||||||
await ScanSelfBot();
|
|
||||||
await ScanFakePlayerPlugins();
|
|
||||||
await ScanMinerJar();
|
|
||||||
|
|
||||||
return results.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
Moonlight/App/Services/MalwareScanService.cs
Normal file
49
Moonlight/App/Services/MalwareScanService.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.MalwareScans;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
using Moonlight.App.Services.Plugins;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class MalwareScanService //TODO: Make this moddable using plugins
|
||||||
|
{
|
||||||
|
private readonly PluginService PluginService;
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public MalwareScanService(PluginService pluginService, IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
PluginService = pluginService;
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MalwareScanResult[]> Perform(Server server)
|
||||||
|
{
|
||||||
|
var defaultScans = new List<MalwareScan>
|
||||||
|
{
|
||||||
|
new SelfBotScan(),
|
||||||
|
new MinerJarScan(),
|
||||||
|
new SelfBotCodeScan(),
|
||||||
|
new FakePlayerPluginScan()
|
||||||
|
};
|
||||||
|
|
||||||
|
var scans = await PluginService.BuildMalwareScans(defaultScans.ToArray());
|
||||||
|
|
||||||
|
var results = new List<MalwareScanResult>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Plugin;
|
using Moonlight.App.Plugin;
|
||||||
using Moonlight.App.Plugin.UI.Servers;
|
using Moonlight.App.Plugin.UI.Servers;
|
||||||
using Moonlight.App.Plugin.UI.Webspaces;
|
using Moonlight.App.Plugin.UI.Webspaces;
|
||||||
@@ -97,4 +98,17 @@ public class PluginService
|
|||||||
await plugin.OnBuildServices.Invoke(serviceCollection);
|
await plugin.OnBuildServices.Invoke(serviceCollection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MalwareScan[]> BuildMalwareScans(MalwareScan[] defaultScans)
|
||||||
|
{
|
||||||
|
var scanList = defaultScans.ToList();
|
||||||
|
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
if (plugin.OnBuildMalwareScans != null)
|
||||||
|
scanList = await plugin.OnBuildMalwareScans.Invoke(scanList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanList.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,8 @@ using Moonlight.App.Helpers.Wings;
|
|||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
|
using Moonlight.App.Services.Background;
|
||||||
|
using Moonlight.App.Services.Plugins;
|
||||||
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
@@ -32,6 +34,11 @@ public class ServerService
|
|||||||
private readonly DateTimeService DateTimeService;
|
private readonly DateTimeService DateTimeService;
|
||||||
private readonly EventSystem Event;
|
private readonly EventSystem Event;
|
||||||
|
|
||||||
|
// We inject the dependencies for the malware scan service here because otherwise it may result in
|
||||||
|
// a circular dependency injection which will crash the app
|
||||||
|
private readonly PluginService PluginService;
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
public ServerService(
|
public ServerService(
|
||||||
ServerRepository serverRepository,
|
ServerRepository serverRepository,
|
||||||
WingsApiHelper wingsApiHelper,
|
WingsApiHelper wingsApiHelper,
|
||||||
@@ -45,7 +52,9 @@ public class ServerService
|
|||||||
NodeAllocationRepository nodeAllocationRepository,
|
NodeAllocationRepository nodeAllocationRepository,
|
||||||
DateTimeService dateTimeService,
|
DateTimeService dateTimeService,
|
||||||
EventSystem eventSystem,
|
EventSystem eventSystem,
|
||||||
Repository<ServerVariable> serverVariablesRepository)
|
Repository<ServerVariable> serverVariablesRepository,
|
||||||
|
PluginService pluginService,
|
||||||
|
IServiceScopeFactory serviceScopeFactory)
|
||||||
{
|
{
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
WingsApiHelper = wingsApiHelper;
|
WingsApiHelper = wingsApiHelper;
|
||||||
@@ -60,6 +69,8 @@ public class ServerService
|
|||||||
DateTimeService = dateTimeService;
|
DateTimeService = dateTimeService;
|
||||||
Event = eventSystem;
|
Event = eventSystem;
|
||||||
ServerVariablesRepository = serverVariablesRepository;
|
ServerVariablesRepository = serverVariablesRepository;
|
||||||
|
PluginService = pluginService;
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Server EnsureNodeData(Server s)
|
private Server EnsureNodeData(Server s)
|
||||||
@@ -99,6 +110,26 @@ public class ServerService
|
|||||||
{
|
{
|
||||||
Server server = EnsureNodeData(s);
|
Server server = EnsureNodeData(s);
|
||||||
|
|
||||||
|
if (ConfigService.Get().Moonlight.Security.MalwareCheckOnStart && signal == PowerSignal.Start ||
|
||||||
|
signal == PowerSignal.Restart)
|
||||||
|
{
|
||||||
|
var results = await new MalwareScanService(
|
||||||
|
PluginService,
|
||||||
|
ServiceScopeFactory
|
||||||
|
).Perform(server);
|
||||||
|
|
||||||
|
if (results.Any())
|
||||||
|
{
|
||||||
|
var resultText = string.Join(" ", results.Select(x => x.Title));
|
||||||
|
|
||||||
|
Logger.Warn($"Found malware on server {server.Uuid}. Results: " + resultText);
|
||||||
|
|
||||||
|
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",
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var rawSignal = signal.ToString().ToLower();
|
var rawSignal = signal.ToString().ToLower();
|
||||||
|
|
||||||
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPower()
|
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/power", new ServerPower()
|
||||||
@@ -420,10 +451,7 @@ public class ServerService
|
|||||||
await new Retry()
|
await new Retry()
|
||||||
.Times(3)
|
.Times(3)
|
||||||
.At(x => x.Message.Contains("A task was canceled"))
|
.At(x => x.Message.Contains("A task was canceled"))
|
||||||
.Call(async () =>
|
.Call(async () => { await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null); });
|
||||||
{
|
|
||||||
await WingsApiHelper.Delete(server.Node, $"api/servers/{server.Uuid}", null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (WingsException e)
|
catch (WingsException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<TicketServerService>();
|
builder.Services.AddSingleton<TicketServerService>();
|
||||||
builder.Services.AddScoped<TicketClientService>();
|
builder.Services.AddScoped<TicketClientService>();
|
||||||
builder.Services.AddScoped<TicketAdminService>();
|
builder.Services.AddScoped<TicketAdminService>();
|
||||||
|
builder.Services.AddScoped<MalwareScanService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<SessionClientService>();
|
builder.Services.AddScoped<SessionClientService>();
|
||||||
builder.Services.AddSingleton<SessionServerService>();
|
builder.Services.AddSingleton<SessionServerService>();
|
||||||
@@ -245,7 +246,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<StatisticsCaptureService>();
|
builder.Services.AddSingleton<StatisticsCaptureService>();
|
||||||
builder.Services.AddSingleton<DiscordNotificationService>();
|
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||||
builder.Services.AddSingleton<CleanupService>();
|
builder.Services.AddSingleton<CleanupService>();
|
||||||
builder.Services.AddSingleton<MalwareScanService>();
|
builder.Services.AddSingleton<MalwareBackgroundScanService>();
|
||||||
builder.Services.AddSingleton<TelemetryService>();
|
builder.Services.AddSingleton<TelemetryService>();
|
||||||
builder.Services.AddSingleton<TempMailService>();
|
builder.Services.AddSingleton<TempMailService>();
|
||||||
builder.Services.AddSingleton<DdosProtectionService>();
|
builder.Services.AddSingleton<DdosProtectionService>();
|
||||||
@@ -297,7 +298,7 @@ namespace Moonlight
|
|||||||
_ = app.Services.GetRequiredService<DiscordBotService>();
|
_ = app.Services.GetRequiredService<DiscordBotService>();
|
||||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||||
_ = app.Services.GetRequiredService<MalwareScanService>();
|
_ = app.Services.GetRequiredService<MalwareBackgroundScanService>();
|
||||||
_ = app.Services.GetRequiredService<TelemetryService>();
|
_ = app.Services.GetRequiredService<TelemetryService>();
|
||||||
_ = app.Services.GetRequiredService<TempMailService>();
|
_ = app.Services.GetRequiredService<TempMailService>();
|
||||||
_ = app.Services.GetRequiredService<DdosProtectionService>();
|
_ = app.Services.GetRequiredService<DdosProtectionService>();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
@using Moonlight.App.Events
|
@using Moonlight.App.Events
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
|
|
||||||
@inject MalwareScanService MalwareScanService
|
@inject MalwareBackgroundScanService MalwareBackgroundScanService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject EventSystem Event
|
@inject EventSystem Event
|
||||||
|
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if (MalwareScanService.IsRunning)
|
@if (MalwareBackgroundScanService.IsRunning)
|
||||||
{
|
{
|
||||||
<span class="fs-3 spinner-border align-middle me-3"></span>
|
<span class="fs-3 spinner-border align-middle me-3"></span>
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class="fs-3">@(MalwareScanService.Status)</span>
|
<span class="fs-3">@(MalwareBackgroundScanService.Status)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
@if (MalwareScanService.IsRunning)
|
@if (MalwareBackgroundScanService.IsRunning)
|
||||||
{
|
{
|
||||||
<button class="btn btn-success disabled">
|
<button class="btn btn-success disabled">
|
||||||
<TL>Scan in progress</TL>
|
<TL>Scan in progress</TL>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
{
|
{
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="scanAllServers" @bind="MalwareScanService.ScanAllServers">
|
<input class="form-check-input" type="checkbox" id="scanAllServers" @bind="MalwareBackgroundScanService.ScanAllServers">
|
||||||
<label class="form-check-label" for="scanAllServers">
|
<label class="form-check-label" for="scanAllServers">
|
||||||
<TL>Scan all servers</TL>
|
<TL>Scan all servers</TL>
|
||||||
</label>
|
</label>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
|
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
|
||||||
CssClasses="btn-success"
|
CssClasses="btn-success"
|
||||||
OnClick="MalwareScanService.Start">
|
OnClick="MalwareBackgroundScanService.Start">
|
||||||
</WButton>
|
</WButton>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -124,9 +124,9 @@
|
|||||||
{
|
{
|
||||||
ScanResults.Clear();
|
ScanResults.Clear();
|
||||||
|
|
||||||
lock (MalwareScanService.ScanResults)
|
lock (MalwareBackgroundScanService.ScanResults)
|
||||||
{
|
{
|
||||||
foreach (var result in MalwareScanService.ScanResults)
|
foreach (var result in MalwareBackgroundScanService.ScanResults)
|
||||||
{
|
{
|
||||||
ScanResults.Add(result.Key, result.Value);
|
ScanResults.Add(result.Key, result.Value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
@using Moonlight.App.Plugin.UI
|
@using Moonlight.App.Plugin.UI
|
||||||
@using Moonlight.App.Plugin.UI.Servers
|
@using Moonlight.App.Plugin.UI.Servers
|
||||||
@using Moonlight.App.Services.Sessions
|
@using Moonlight.App.Services.Sessions
|
||||||
|
@using Moonlight.App.ApiClients.Wings
|
||||||
|
|
||||||
@inject SmartTranslateService TranslationService
|
@inject SmartTranslateService TranslationService
|
||||||
|
@inject ServerService ServerService
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
|
|
||||||
<div class="align-items-center">
|
<div class="align-items-center">
|
||||||
@@ -177,22 +179,22 @@
|
|||||||
|
|
||||||
private async Task Start()
|
private async Task Start()
|
||||||
{
|
{
|
||||||
await Console.SetPowerState("start");
|
await ServerService.SetPowerState(CurrentServer, PowerSignal.Start);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Stop()
|
private async Task Stop()
|
||||||
{
|
{
|
||||||
await Console.SetPowerState("stop");
|
await ServerService.SetPowerState(CurrentServer, PowerSignal.Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Kill()
|
private async Task Kill()
|
||||||
{
|
{
|
||||||
await Console.SetPowerState("kill");
|
await ServerService.SetPowerState(CurrentServer, PowerSignal.Kill);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Restart()
|
private async Task Restart()
|
||||||
{
|
{
|
||||||
await Console.SetPowerState("restart");
|
await ServerService.SetPowerState(CurrentServer, PowerSignal.Restart);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user