Tester made a file flooder... rate limits shall be implemented now!

This commit is contained in:
trouper
2025-03-20 12:12:14 -05:00
parent 8299d75adf
commit 000dc63035
20 changed files with 138 additions and 21 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -12,6 +12,7 @@ softdepend:
- ViaBackwards
- ViaRewind
- Geyser-Spigot
- NoChatReports
load: POSTWORLD
permissions:
sentinel.admin:

View File

@@ -4,6 +4,7 @@ import io.github.itzispyder.pdk.utils.misc.config.JsonSerializable;
import me.trouper.sentinel.Sentinel;
import java.io.File;
import java.util.List;
public class NBTConfig implements JsonSerializable<NBTConfig> {
@Override
@@ -13,6 +14,16 @@ public class NBTConfig implements JsonSerializable<NBTConfig> {
return file;
}
public RateLimit rateLimit = new RateLimit();
public class RateLimit {
public int rateLimitBytes = 16348;
public int byteDecay = 1024;
public int rateLimitItems = 10;
public int itemDecay = 2;
public List<String> punishmentCommands = List.of("kick %player% Internal Exception: io.netty.handler.codec.DecoderException: java.lang.RuntimeException: Tried to read NBT tag that was too big; tried to allocate 28391038bytes where max allowed: 16348");
}
public boolean allowName = true;
public boolean allowLore = true;
public boolean allowAttributes = false;
@@ -21,6 +32,8 @@ public class NBTConfig implements JsonSerializable<NBTConfig> {
public boolean allowCustomTools = false;
public boolean allowBooks = false;
public boolean allowRecursion = true;
public int maxCustomData = 64;
public int globalMaxEnchant = 5;
public int maxMending = 1;
public int maxUnbreaking = 3;

View File

@@ -1,11 +1,13 @@
package me.trouper.sentinel.server.events.violations.players;
import io.github.itzispyder.pdk.plugin.gui.CustomGui;
import io.github.itzispyder.pdk.utils.misc.Pair;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.storage.NBTStorage;
import me.trouper.sentinel.server.events.violations.AbstractViolation;
import me.trouper.sentinel.server.functions.helpers.ActionConfiguration;
import me.trouper.sentinel.server.functions.itemchecks.ItemCheck;
import me.trouper.sentinel.server.functions.itemchecks.RateLimitCheck;
import me.trouper.sentinel.server.gui.Items;
import me.trouper.sentinel.server.gui.MainGUI;
import me.trouper.sentinel.server.gui.config.AntiNukeGUI;
@@ -28,18 +30,36 @@ public class CreativeHotbar extends AbstractViolation {
private void onNBTPull(InventoryCreativeEvent e) {
//ServerUtils.verbose("NBT: Detected creative mode action");
if (!Sentinel.getInstance().getDirector().io.violationConfig.creativeHotbarAction.enabled) return;
ServerUtils.verbose("NBT: Enabled");
//ServerUtils.verbose("NBT: Enabled");
if (!(e.getWhoClicked() instanceof Player p)) return;
ServerUtils.verbose("NBT: Clicker is a player");
//ServerUtils.verbose("NBT: Clicker is a player");
if (e.getCursor() == null) return; // Well it threw an exception during testing, so it isn't always false!
ServerUtils.verbose("NBT: Cursor isn't null");
//ServerUtils.verbose("NBT: Cursor isn't null");
ItemStack i = e.getCursor();
if (PlayerUtils.isTrusted(p)) return;
ServerUtils.verbose("NBT: Not trusted");
//ServerUtils.verbose("NBT: Not trusted");
if (e.getCursor().getItemMeta() == null) return;
ServerUtils.verbose("NBT: Cursor has meta");
//ServerUtils.verbose("NBT: Cursor has meta");
if (!(i.hasItemMeta() && i.getItemMeta() != null)) return;
ServerUtils.verbose("NBT: Item has meta");
if (!new RateLimitCheck().passes(new Pair<>(p,i))) {
ServerUtils.verbose("Player flags rate limit, performing action");
ActionConfiguration.Builder config = new ActionConfiguration.Builder()
.setEvent(e)
.setPlayer(p)
.cancel(true)
.punish(true)
.deop(Sentinel.getInstance().getDirector().io.violationConfig.creativeHotbarAction.deop)
.setPunishmentCommands(Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.punishmentCommands);
runActions(
Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.grab, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.nbtItem),
Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.grab, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.nbtItem),
generatePlayerInfo(p),
config
);
return;
}
if (new ItemCheck().passes(i)) return;
ServerUtils.verbose("NBT: Item doesn't pass, performing action");

View File

@@ -62,12 +62,6 @@ public class PluginCloakingPacket implements PacketListener {
}
}
}
case PacketType.Play.Client.CHAT_COMMAND, PacketType.Play.Client.CHAT_COMMAND_UNSIGNED -> {
WrapperPlayClientChatCommandUnsigned wrapper = new WrapperPlayClientChatCommandUnsigned(event);
WrapperPlayClientChatCommand wrappers = new WrapperPlayClientChatCommand(event);
wrapper.getCommand();
wrappers.getCommand();
}
default -> {}
}
}

View File

@@ -45,7 +45,6 @@ public class ActionConfiguration {
this.punishmentCommands = builder.punishmentCommands;
this.logToDiscord = builder.logToDiscord;
this.actionNode = builder.actionNode;
// Removed the actions being run here to prevent double execution
}
public Player getPlayer() {

View File

@@ -2,6 +2,7 @@ package me.trouper.sentinel.server.functions.itemchecks;
import de.tr7zw.changeme.nbtapi.NBT;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.config.NBTConfig;
import me.trouper.sentinel.utils.InventoryUtils;
import me.trouper.sentinel.utils.ServerUtils;
import org.bukkit.Material;
@@ -17,6 +18,7 @@ public class ItemCheck extends AbstractCheck<ItemStack> {
@Override
public boolean passes(ItemStack item) {
ServerUtils.verbose("Checking item: " + item.getType().name());
NBTConfig config = Sentinel.getInstance().getDirector().io.nbtConfig;
// No metadata? Nothing to check.
if (item.getItemMeta() == null) {
@@ -38,11 +40,11 @@ public class ItemCheck extends AbstractCheck<ItemStack> {
// NBT-based checks (e.g. custom consumables/tools).
var nbt = NBT.itemStackToNBT(item);
var components = nbt.getCompound("components");
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowCustomConsumables && components.getCompound("minecraft:consumable") != null) {
if (!config.allowCustomConsumables && components.getCompound("minecraft:consumable") != null) {
ServerUtils.verbose("Item is consumable and not allowed.");
return false;
}
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowCustomTools && components.getCompound("minecraft:tool") != null) {
if (!config.allowCustomTools && components.getCompound("minecraft:tool") != null) {
ServerUtils.verbose("Item is custom tool and not allowed.");
return false;
}
@@ -148,32 +150,32 @@ public class ItemCheck extends AbstractCheck<ItemStack> {
}
// Name, lore, potion, attribute and enchantment checks.
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowName && meta.hasDisplayName()) {
if (!config.allowName && meta.hasDisplayName()) {
ServerUtils.verbose("Custom names not allowed.");
return false;
}
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowLore && meta.hasLore()) {
if (!config.allowLore && meta.hasLore()) {
ServerUtils.verbose("Custom lore not allowed.");
return false;
}
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowPotions &&
if (!config.allowPotions &&
(item.getType().equals(Material.POTION) ||
item.getType().equals(Material.SPLASH_POTION) ||
item.getType().equals(Material.LINGERING_POTION))) {
ServerUtils.verbose("Potions not allowed.");
return false;
}
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowAttributes && meta.hasAttributeModifiers()) {
if (!config.allowAttributes && meta.hasAttributeModifiers()) {
ServerUtils.verbose("Attribute modifiers not allowed.");
return false;
}
if (Sentinel.getInstance().getDirector().io.nbtConfig.globalMaxEnchant != 0 && new EnchantmentCheck().hasIllegalEnchants(item)) {
if (config.globalMaxEnchant != 0 && new EnchantmentCheck().hasIllegalEnchants(item)) {
ServerUtils.verbose("Illegal enchantments found.");
return false;
}
// Recursion check for use-remainder items.
if (meta.hasUseRemainder()) {
if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowRecursion) {
if (!config.allowRecursion) {
ServerUtils.verbose("Recursion not allowed.");
return false;
}

View File

@@ -0,0 +1,85 @@
package me.trouper.sentinel.server.functions.itemchecks;
import de.tr7zw.changeme.nbtapi.NBTItem;
import io.github.itzispyder.pdk.utils.misc.Pair;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.utils.ServerUtils;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitTask;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class RateLimitCheck extends AbstractCheck<Pair<Player,ItemStack>> {
public static Map<UUID, Integer> dataUsed = new HashMap<>();
public static Map<UUID, Integer> itemsUsed = new HashMap<>();
@Override
public boolean passes(Pair<Player,ItemStack> input) {
Player player = input.left;
UUID uuid = player.getUniqueId();
ItemStack item = input.right;
return itemLimit(player,uuid,item) && dataLimit(player,uuid,item);
}
private boolean itemLimit(Player player, UUID uuid, ItemStack item) {
int currentUsed = itemsUsed.getOrDefault(uuid,0);
ServerUtils.verbose("Current Player used items: " + currentUsed);
currentUsed++;
itemsUsed.put(uuid,currentUsed);
return currentUsed <= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitItems;
}
private boolean dataLimit(Player player, UUID uuid, ItemStack item) {
int itemData = 0;
int currentData = dataUsed.getOrDefault(uuid,0);
ServerUtils.verbose("Current Player used data: " + currentData);
try {
NBTItem nbt = new NBTItem(item);
itemData = nbt.toString().length();
ServerUtils.verbose("Item data: " + itemData);
currentData += itemData;
} catch (Exception e) {
Sentinel.getInstance().getLogger().warning("Could not determine size of item. Blocking.");
Sentinel.getInstance().getLogger().warning(Arrays.toString(e.getStackTrace()));
return false;
}
dataUsed.put(uuid,currentData);
ServerUtils.verbose("New Player used data: " + currentData);
return currentData <= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitBytes;
}
public static void decayData() {
for (UUID uuid : dataUsed.keySet()) {
int currentData = dataUsed.get(uuid);
if (currentData > 0) {
currentData -= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.byteDecay;
dataUsed.put(uuid, Math.max(0, currentData));
}
}
}
public static void decayItems() {
for (UUID uuid : itemsUsed.keySet()) {
int currentItems = itemsUsed.get(uuid);
if (currentItems > 0) {
currentItems -= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.itemDecay;
itemsUsed.put(uuid, Math.max(0, currentItems));
}
}
}
}

View File

@@ -28,6 +28,7 @@ import me.trouper.sentinel.server.events.violations.entities.CommandMinecartPlac
import me.trouper.sentinel.server.events.violations.entities.CommandMinecartUse;
import me.trouper.sentinel.server.functions.chatfilter.profanity.ProfanityFilter;
import me.trouper.sentinel.server.functions.chatfilter.spam.SpamFilter;
import me.trouper.sentinel.server.functions.itemchecks.RateLimitCheck;
import me.trouper.sentinel.utils.Text;
import org.bukkit.Bukkit;
@@ -183,6 +184,8 @@ public final class Loader {
Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), SpamFilter::decayHeat,0, 20);
Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), ProfanityFilter::decayScore,0,1200);
Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), WandEvents::handleDisplay,0,1);
Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), RateLimitCheck::decayData,0,20*60);
Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), RateLimitCheck::decayItems,0,200);
if (Sentinel.getInstance().getDirector().io.mainConfig.backdoorDetection.enabled) Sentinel.getInstance().getDirector().backdoorDetection.init();