Merge branch 'main' into AddLiveStatistics

This commit is contained in:
Marcel Baumgartner
2023-08-29 16:10:16 +02:00
committed by GitHub
47 changed files with 8478 additions and 3387 deletions

View File

@@ -304,6 +304,14 @@ public class ConfigV1
public int BlockIpDuration { get; set; } = 15;
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
[JsonProperty("BlockDatacenterIps")]
[Description("If this option is enabled, users with an ip from datacenters will not be able to access the panel")]
public bool BlockDatacenterIps { get; set; } = true;
[JsonProperty("AllowCloudflareIps")]
[Description("Allow cloudflare ips to bypass the datacenter ip check")]
public bool AllowCloudflareIps { get; set; } = false;
}
public class ReCaptchaData

View File

@@ -218,6 +218,16 @@ public class WingsConsole : IDisposable
break;
case "install output":
if (ServerState != ServerState.Installing)
{
// Because wings is sending "install output" events BEFORE
// sending the "install started" event,
// we need to set the install state here
// See https://github.com/pterodactyl/panel/issues/4853
// for more details
await UpdateServerState(ServerState.Installing);
}
foreach (var line in eventData.Args)
{
await SaveMessage(line);

View File

@@ -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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
{
var serverService = serviceProvider.GetRequiredService<ServerService>();
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<MalwareScanResult>();
return null;
}
}

View File

@@ -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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
{
var serverService = serviceProvider.GetRequiredService<ServerService>();
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<MalwareScanResult>();
return null;
}
}

View File

@@ -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<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
{
var serverService = serviceProvider.GetRequiredService<ServerService>();
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") || x.Name.EndsWith(".yml")) || x.Name == "bed"))
{
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;
}
}

View File

@@ -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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
{
var serverService = serviceProvider.GetRequiredService<ServerService>();
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<MalwareScanResult>();
return null;
}
}

View File

@@ -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<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider)
public override async Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider)
{
var serverService = serviceProvider.GetRequiredService<ServerService>();
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<MalwareScanResult>();
return null;
}
}

View File

@@ -6,5 +6,5 @@ public abstract class MalwareScan
{
public abstract string Name { get; }
public abstract string Description { get; }
public abstract Task<MalwareScanResult[]> Scan(Server server, IServiceProvider serviceProvider);
public abstract Task<MalwareScanResult?> Scan(Server server, IServiceProvider serviceProvider);
}

View File

@@ -15,7 +15,7 @@ public class MalwareBackgroundScanService
public bool IsRunning => !ScanTask?.IsCompleted ?? false;
public bool ScanAllServers { get; set; }
public readonly Dictionary<Server, MalwareScanResult[]> ScanResults;
public readonly Dictionary<Server, MalwareScanResult> 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++;

View File

@@ -1,15 +1,11 @@
using Moonlight.App.Helpers;
using Octokit;
namespace Moonlight.App.Services.Files;
public class StorageService
{
public StorageService()
{
EnsureCreated();
}
public void EnsureCreated()
public async Task EnsureCreated()
{
Directory.CreateDirectory(PathBuilder.Dir("storage", "uploads"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "configs"));
@@ -17,13 +13,16 @@ public class StorageService
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
await UpdateResources();
return;
if (IsEmpty(PathBuilder.Dir("storage", "resources")))
{
Logger.Info("Default resources not found. Copying default resources");
CopyFilesRecursively(
PathBuilder.Dir("defaultstorage", "resources"),
PathBuilder.Dir("defaultstorage", "resources"),
PathBuilder.Dir("storage", "resources")
);
}
@@ -31,18 +30,83 @@ public class StorageService
if (IsEmpty(PathBuilder.Dir("storage", "configs")))
{
Logger.Info("Default configs not found. Copying default configs");
CopyFilesRecursively(
PathBuilder.Dir("defaultstorage", "configs"),
PathBuilder.Dir("defaultstorage", "configs"),
PathBuilder.Dir("storage", "configs")
);
}
}
private async Task UpdateResources()
{
try
{
Logger.Info("Checking resources");
var client = new GitHubClient(
new ProductHeaderValue("Moonlight-Panel"));
var user = "Moonlight-Panel";
var repo = "Resources";
var resourcesDir = PathBuilder.Dir("storage", "resources");
async Task CopyDirectory(string dirPath, string localDir)
{
IReadOnlyList<RepositoryContent> contents;
if (string.IsNullOrEmpty(dirPath))
contents = await client.Repository.Content.GetAllContents(user, repo);
else
contents = await client.Repository.Content.GetAllContents(user, repo, dirPath);
foreach (var content in contents)
{
string localPath = Path.Combine(localDir, content.Name);
if (content.Type == ContentType.File)
{
if (content.Name.EndsWith(".gitattributes"))
continue;
if (File.Exists(localPath) && !content.Name.EndsWith(".lang"))
continue;
if (content.Name.EndsWith(".lang") && File.Exists(localPath) &&
new FileInfo(localPath).Length == content.Size)
continue;
var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path);
Directory.CreateDirectory(localDir); // Ensure the directory exists
await File.WriteAllBytesAsync(localPath, fileContent);
Logger.Debug($"Synced file '{content.Path}'");
}
else if (content.Type == ContentType.Dir)
{
await CopyDirectory(content.Path, localPath);
}
}
}
await CopyDirectory("", resourcesDir);
}
catch (RateLimitExceededException)
{
Logger.Warn("Unable to sync resources due to your ip being rate-limited by github");
}
catch (Exception e)
{
Logger.Warn("Unable to sync resources");
Logger.Warn(e);
}
}
private bool IsEmpty(string path)
{
return !Directory.EnumerateFileSystemEntries(path).Any();
}
private static void CopyFilesRecursively(string sourcePath, string targetPath)
{
//Now Create all of the directories
@@ -52,7 +116,7 @@ public class StorageService
}
//Copy all the files & Replaces any files with the same name
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories))
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
{
File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
}

View File

@@ -0,0 +1,91 @@
using Moonlight.App.Helpers;
using Whois.NET;
namespace Moonlight.App.Services;
public class IpVerificationService
{
private readonly ConfigService ConfigService;
public IpVerificationService(ConfigService configService)
{
ConfigService = configService;
}
public async Task<bool> IsDatacenterOrVpn(string ip)
{
if (!ConfigService.Get().Moonlight.Security.BlockDatacenterIps)
return false;
if (string.IsNullOrEmpty(ip))
return false;
var datacenterNames = new List<string>()
{
"amazon",
"aws",
"microsoft",
"azure",
"google",
"google cloud",
"gcp",
"digitalocean",
"linode",
"vultr",
"ovh",
"ovhcloud",
"alibaba",
"oracle",
"ibm cloud",
"bluehost",
"godaddy",
"rackpace",
"hetzner",
"tencent",
"scaleway",
"softlayer",
"dreamhost",
"a2 hosting",
"inmotion hosting",
"red hat openstack",
"kamatera",
"hostgator",
"siteground",
"greengeeks",
"liquidweb",
"joyent",
"aruba",
"interoute",
"fastcomet",
"rosehosting",
"lunarpages",
"fatcow",
"jelastic",
"datacamp"
};
if(!ConfigService.Get().Moonlight.Security.AllowCloudflareIps)
datacenterNames.Add("cloudflare");
try
{
var response = await WhoisClient.QueryAsync(ip);
var responseText = response.Raw.ToLower();
foreach (var name in datacenterNames)
{
if (responseText.Contains(name))
{
Logger.Debug(name);
return true;
}
}
}
catch (Exception)
{
return false;
}
return false;
}
}

View File

@@ -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<MalwareScanResult[]> Perform(Server server)
public async Task<MalwareScanResult?> Perform(Server server)
{
var defaultScans = new List<MalwareScan>
{
new SelfBotScan(),
new MinerJarScan(),
new SelfBotCodeScan(),
new FakePlayerPluginScan()
new FakePlayerPluginScan(),
new MinerScan()
};
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);
}
}
if (result != null)
return result;
}
return results.ToArray();
return null;
}
}

View File

@@ -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);
}
}