From 38da70016886e684663e4bf3f9bbbcdf905fbd57 Mon Sep 17 00:00:00 2001 From: PiggyPiglet Date: Fri, 21 Nov 2025 18:56:49 +0800 Subject: [PATCH] Add checks for known malicious expansion checksums before expansion load --- .../placeholderapi/PlaceholderAPIPlugin.java | 17 +++- .../commands/impl/local/CommandReload.java | 5 +- .../configuration/PlaceholderAPIConfig.java | 4 + .../util/MaliciousExpansionCheck.java | 81 +++++++++++++++++++ src/main/resources/config.yml | 1 + 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/main/java/me/clip/placeholderapi/util/MaliciousExpansionCheck.java diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java index c374aa2..b176a6d 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java @@ -21,7 +21,6 @@ package me.clip.placeholderapi; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import me.clip.placeholderapi.commands.PlaceholderCommandRouter; @@ -34,6 +33,7 @@ import me.clip.placeholderapi.listeners.ServerLoadEventListener; import me.clip.placeholderapi.scheduler.UniversalScheduler; import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler; import me.clip.placeholderapi.updatechecker.UpdateChecker; +import me.clip.placeholderapi.util.MaliciousExpansionCheck; import me.clip.placeholderapi.util.Msg; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bstats.bukkit.Metrics; @@ -92,6 +92,7 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { private final TaskScheduler scheduler = UniversalScheduler.getScheduler(this); private BukkitAudiences adventure; + private boolean malwareDetected = false; /** @@ -150,13 +151,23 @@ public final class PlaceholderAPIPlugin extends JavaPlugin { @Override public void onLoad() { - instance = this; - saveDefaultConfig(); + + malwareDetected = new MaliciousExpansionCheck(this).runChecks(); + + if (malwareDetected) { + return; + } + + instance = this; } @Override public void onEnable() { + if (malwareDetected) { + return; + } + setupCommand(); setupMetrics(); setupExpansions(); diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandReload.java b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandReload.java index 44e1b43..d07c077 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandReload.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandReload.java @@ -23,6 +23,7 @@ package me.clip.placeholderapi.commands.impl.local; import java.util.List; import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.commands.PlaceholderCommand; +import me.clip.placeholderapi.util.MaliciousExpansionCheck; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; @@ -37,7 +38,9 @@ public final class CommandReload extends PlaceholderCommand { public void evaluate(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final CommandSender sender, @NotNull final String alias, @NotNull @Unmodifiable final List params) { - plugin.reloadConf(sender); + if (!new MaliciousExpansionCheck(plugin).runChecks()) { + plugin.reloadConf(sender); + } } } diff --git a/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java index 0dc895b..26c0213 100644 --- a/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java +++ b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java @@ -86,4 +86,8 @@ public final class PlaceholderAPIConfig { return plugin.getConfig().getString("boolean.false", "false"); } + public boolean detectMaliciousExpansions() { + return plugin.getConfig().getBoolean("detect_malicious_expansions", true); + } + } diff --git a/src/main/java/me/clip/placeholderapi/util/MaliciousExpansionCheck.java b/src/main/java/me/clip/placeholderapi/util/MaliciousExpansionCheck.java new file mode 100644 index 0000000..9fb74b8 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/MaliciousExpansionCheck.java @@ -0,0 +1,81 @@ +package me.clip.placeholderapi.util; + +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import com.google.common.io.Resources; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import me.clip.placeholderapi.configuration.PlaceholderAPIConfig; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public final class MaliciousExpansionCheck { + private static final String MESSAGE = + "\n###############################################\n" + + "###############################################\n" + + "PlaceholderAPI performs checks at startup and /papi reload for known malicious expansions. If you're seeing this message, there are the following malicious expansions in plugins/PlaceholderAPI/expansions.\n" + + "%s" + + "To prevent further infection PlaceholderAPI has stopped the server.\n" + + "Best practice is a complete system wipe and reinstall of your server software and plugins to be safe.\n" + + "###############################################\n" + + "###############################################"; + + private final PlaceholderAPIPlugin main; + + public MaliciousExpansionCheck(@NotNull final PlaceholderAPIPlugin main) { + this.main = main; + } + + public boolean runChecks() { + if (!main.getPlaceholderAPIConfig().detectMaliciousExpansions()) { + return false; + } + + final File expansionsFolder = new File(main.getDataFolder(), "expansions"); + + if (!expansionsFolder.exists()) { + return false; + } + + final Set knownMalware; + + try { + final String malware = Resources.toString(new URL("https://check.placeholderapi.com"), StandardCharsets.UTF_8); + knownMalware = Arrays.stream(malware.split("\n")).collect(Collectors.toSet()); + } catch (Exception e) { + main.getLogger().log(Level.SEVERE, "Failed to download anti malware hash check list from https://check.placeholderapi.com", e); + return false; + } + + final Set malwarePaths = new HashSet<>(); + + for (File file : expansionsFolder.listFiles()) { + try { + final String hash = Hashing.sha256().hashBytes(Files.asByteSource(file).read()).toString(); + + if (knownMalware.contains(hash)) { + malwarePaths.add(file.getAbsolutePath()); + } + } catch (Exception e) { + main.getLogger().log(Level.SEVERE, "Error occurred while trying to read " + file.getAbsolutePath(), e); + } + } + + if (malwarePaths.isEmpty()) { + return false; + } + + main.getLogger().severe(String.format(MESSAGE, malwarePaths.stream().map(p -> "HASH OF " + p + " MATCHES KNOWN MALICIOUS EXPANSION DELETE IMMEDIATELY\n").collect(Collectors.joining()))); + + main.getServer().shutdown(); + return true; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 26f937a..0fe9cda 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -15,4 +15,5 @@ boolean: 'true': 'yes' 'false': 'no' date_format: MM/dd/yy HH:mm:ss +detect_malicious_expansions: true debug: false