238 lines
7.7 KiB
C#
238 lines
7.7 KiB
C#
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();
|
|
}
|
|
} |