196 lines
6.5 KiB
C#
196 lines
6.5 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 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()
|
|
{
|
|
IsRunning = true;
|
|
Status = "Clearing last results";
|
|
await Event.Emit("malwareScan.status", IsRunning);
|
|
|
|
lock (ScanResults)
|
|
{
|
|
ScanResults.Clear();
|
|
}
|
|
|
|
await Event.Emit("malwareScan.result");
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Perform scan
|
|
var resultsMapped = new Dictionary<Server, MalwareScanResult[]>();
|
|
foreach (var mapping in containerServerMapped)
|
|
{
|
|
Logger.Verbose($"Scanning server {mapping.Key.Name} for malware");
|
|
|
|
Status = $"Scanning server {mapping.Key.Name} for malware";
|
|
await Event.Emit("malwareScan.status", IsRunning);
|
|
|
|
var results = await PerformScanOnServer(mapping.Key, mapping.Value);
|
|
|
|
if (results.Any())
|
|
{
|
|
resultsMapped.Add(mapping.Key, results);
|
|
Logger.Verbose($"{results.Length} findings on server {mapping.Key.Name}");
|
|
}
|
|
}
|
|
|
|
Logger.Verbose($"Scan complete. Detected {resultsMapped.Count} servers with findings");
|
|
|
|
IsRunning = false;
|
|
Status = $"Scan complete. Detected {resultsMapped.Count} servers with findings";
|
|
await Event.Emit("malwareScan.status", IsRunning);
|
|
|
|
lock (ScanResults)
|
|
{
|
|
foreach (var mapping in resultsMapped)
|
|
{
|
|
ScanResults.Add(mapping.Key, mapping.Value);
|
|
}
|
|
}
|
|
|
|
await Event.Emit("malwareScan.result");
|
|
}
|
|
|
|
private async Task<MalwareScanResult[]> PerformScanOnServer(Server server, Container container)
|
|
{
|
|
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 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();
|
|
|
|
return results.ToArray();
|
|
}
|
|
} |