Merge branch 'main' into AddLiveStatistics
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
35
Moonlight/App/MalwareScans/MinerScan.cs
Normal file
35
Moonlight/App/MalwareScans/MinerScan.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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++;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
91
Moonlight/App/Services/IpVerificationService.cs
Normal file
91
Moonlight/App/Services/IpVerificationService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user