diff --git a/build.gradle b/build.gradle index 652d5ce..24f5918 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ minecraft { } repositories { + mavenCentral() maven { name 'spigot-repo' url 'https://hub.spigotmc.org/nexus/content/repositories/public/' @@ -64,6 +65,16 @@ repositories { dependencies { minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855' implementation 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' + + testImplementation('org.junit:junit-bom:5.8.2') + testImplementation('org.junit.jupiter:junit-jupiter:5.8.2') +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } } // Example for how to get properties into the manifest for reading by the runtime.. diff --git a/src/main/java/chloeprime/fix4log4j/Censor.java b/src/main/java/chloeprime/fix4log4j/Censor.java new file mode 100644 index 0000000..8c4a58c --- /dev/null +++ b/src/main/java/chloeprime/fix4log4j/Censor.java @@ -0,0 +1,33 @@ +package chloeprime.fix4log4j; + +/** + * @author Administrator + */ +public class Censor { + private static final String VIRUS_CHARACTERISTIC = "${jndi:}"; + + /** + * Is the given message malicious ? + */ + @SuppressWarnings("AlibabaUndefineMagicConstant") + public static boolean isMaliciousMessage(String message) { + if (!message.contains("$")) { + return false; + } + + int cursor = 0; + for (int i = 0; i < message.length(); i++) { + char c = message.charAt(i); + if (c == VIRUS_CHARACTERISTIC.charAt(cursor)) { + ++cursor; + if (cursor >= VIRUS_CHARACTERISTIC.length()) { + return true; + } + } + } + return false; + } + + private Censor() { + } +} diff --git a/src/main/java/chloeprime/fix4log4j/bukkit/Fix4Log4JBukkitPlugin.java b/src/main/java/chloeprime/fix4log4j/bukkit/Fix4Log4JBukkitPlugin.java index d9116c1..b1030cd 100644 --- a/src/main/java/chloeprime/fix4log4j/bukkit/Fix4Log4JBukkitPlugin.java +++ b/src/main/java/chloeprime/fix4log4j/bukkit/Fix4Log4JBukkitPlugin.java @@ -1,13 +1,19 @@ package chloeprime.fix4log4j.bukkit; +import chloeprime.fix4log4j.Censor; import chloeprime.fix4log4j.Fixer; import org.apache.logging.log4j.LogManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.plugin.java.JavaPlugin; /** * @author ChloePrime */ -public class Fix4Log4JBukkitPlugin extends JavaPlugin { +public class Fix4Log4JBukkitPlugin extends JavaPlugin implements Listener { + private volatile boolean doCensor; @Override public void onLoad() { @@ -19,5 +25,29 @@ public void onLoad() { public void onEnable() { super.onEnable(); Fixer.doRuntimeTest(LogManager.getLogger("Fix4Log4J")); + doCensor = getConfig().getBoolean("censor-user-input"); } + + @EventHandler + public void censorCommand(PlayerCommandPreprocessEvent event) { + if (!doCensor) { + return; + } + if (Censor.isMaliciousMessage(event.getMessage())) { + getLogger().warning("Found malicious player command sent by " + event.getPlayer().getName()); + event.setCancelled(true); + } + } + + @EventHandler + public void censorChatAsync(AsyncPlayerChatEvent event) { + if (!doCensor) { + return; + } + if (Censor.isMaliciousMessage(event.getMessage())) { + getLogger().warning("Found malicious chat message sent by " + event.getPlayer().getName()); + event.setCancelled(true); + } + } + } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..ace5294 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1 @@ +censor-user-input: true \ No newline at end of file diff --git a/src/test/java/chloeprime.fix4log4j/CensorTest.java b/src/test/java/chloeprime.fix4log4j/CensorTest.java new file mode 100644 index 0000000..bec870a --- /dev/null +++ b/src/test/java/chloeprime.fix4log4j/CensorTest.java @@ -0,0 +1,28 @@ +package chloeprime.fix4log4j; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CensorTest { + @Test + void censor() { + String virus = "I'm Bob, ${jndi:ldap://}"; + String virus2 = "${jndi:ldarg://}, Goodbye Bob~"; + String virus3 = "${jndi:}"; + String virus4 = "$§c{§4§Lj§kn§fd§ai§f:§7}"; + String virus5 = "§c$§c{§4§Lj§kn§fd§ai§f:§7}§r"; + + String innocent = "{just non dimensional interface : }"; + String innocent2 = "${just non dimensional interface : "; + + assertTrue(Censor.isMaliciousMessage(virus)); + assertTrue(Censor.isMaliciousMessage(virus2)); + assertTrue(Censor.isMaliciousMessage(virus3)); + assertTrue(Censor.isMaliciousMessage(virus4)); + assertTrue(Censor.isMaliciousMessage(virus5)); + + assertFalse(Censor.isMaliciousMessage(innocent)); + assertFalse(Censor.isMaliciousMessage(innocent2)); + } +}