From e20f03d72e245fa4038552da0c8de4a8ae1313cb Mon Sep 17 00:00:00 2001 From: xnrand Date: Sun, 30 Jul 2017 15:21:27 +0200 Subject: [PATCH] Verify integrity of downloaded jars The bukkit plugins download jar files from Curse and then load those as plugins, i.e. they're running downloaded code. In order to increase security, this commit adds logic to verify the SHA-256 hash of the downloaded jar files. --- .../com/boydti/fawe/bukkit/BukkitMain.java | 12 ++-- .../main/java/com/boydti/fawe/util/Jars.java | 67 +++++++++++++++++++ .../com/boydti/fawe/bukkit/favs/Favs.java | 12 ++-- 3 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 core/src/main/java/com/boydti/fawe/util/Jars.java diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java index bfcd3d92..438d7a74 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java @@ -1,12 +1,10 @@ package com.boydti.fawe.bukkit; import com.boydti.fawe.Fawe; +import com.boydti.fawe.util.Jars; import java.io.File; import java.io.FileOutputStream; import java.lang.reflect.Field; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -57,11 +55,9 @@ public class BukkitMain extends JavaPlugin { if (Bukkit.getPluginManager().getPlugin("WorldEdit") == null) { try { File output = new File(this.getDataFolder().getParentFile(), "WorldEdit.jar"); - URL worldEditUrl = new URL("https://addons.cursecdn.com/files/2431/372/worldedit-bukkit-6.1.7.2.jar"); - try (ReadableByteChannel rbc = Channels.newChannel(worldEditUrl.openStream())) { - try (FileOutputStream fos = new FileOutputStream(output)) { - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - } + byte[] weJar = Jars.WE_B_6_1_7_2.download(); + try (FileOutputStream fos = new FileOutputStream(output)) { + fos.write(weJar); } Bukkit.getPluginManager().loadPlugin(output); } catch (Throwable e) { diff --git a/core/src/main/java/com/boydti/fawe/util/Jars.java b/core/src/main/java/com/boydti/fawe/util/Jars.java new file mode 100644 index 00000000..7bfdd54d --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/Jars.java @@ -0,0 +1,67 @@ +package com.boydti.fawe.util; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import com.boydti.fawe.Fawe; +import com.google.common.io.BaseEncoding; + +public enum Jars { + WE_B_6_1_7_2("https://addons.cursecdn.com/files/2431/372/worldedit-bukkit-6.1.7.2.jar", + "711be37301a327aba4e347131875d0564dbfdc2f41053a12db97f0234661778b", 1726340), + + VS_B_5_171_0("https://addons-origin.cursecdn.com/files/912/511/VoxelSniper-5.171.0-SNAPSHOT.jar", + "292c3b38238e0d8e5f036381d28bccfeb15df67cae53d28b52d066bc6238208f", 3632776); + + public final String url; + public final int filesize; + public final String digest; + + /** + * @param url + * Where this jar can be found and downloaded + * @param digest + * The SHA-256 hexadecimal digest + * @param filesize + * Size of this jar in bytes + */ + private Jars(String url, String digest, int filesize) { + this.url = url; + this.digest = digest; + this.filesize = filesize; + } + + /** download a jar, verify hash, return byte[] containing the jar */ + public byte[] download() throws IOException { + byte[] jarBytes = new byte[this.filesize]; + URL url = new URL(this.url); + try (DataInputStream dis = new DataInputStream(url.openConnection().getInputStream());) { + dis.readFully(jarBytes); + if (dis.read() != -1) { // assert that we've read everything + throw new IllegalStateException("downloaded jar is longer than expected"); + } + + MessageDigest md; + md = MessageDigest.getInstance("SHA-256"); + byte[] thisDigest = md.digest(jarBytes); + byte[] realDigest = BaseEncoding.base16().decode(this.digest.toUpperCase()); + + if (Arrays.equals(thisDigest, realDigest)) { + Fawe.debug("++++ HASH CHECK ++++"); + Fawe.debug(this.url); + Fawe.debug(BaseEncoding.base16().encode(thisDigest)); + return jarBytes; + } else { + throw new IllegalStateException("downloaded jar does not match the hash"); + } + } catch (NoSuchAlgorithmException e) { + // Shouldn't ever happen, Minecraft won't even run on such a JRE + throw new IllegalStateException("Your JRE does not support SHA-256"); + } + + } +} diff --git a/favs/src/main/java/com/boydti/fawe/bukkit/favs/Favs.java b/favs/src/main/java/com/boydti/fawe/bukkit/favs/Favs.java index afcbbfd5..9002a274 100644 --- a/favs/src/main/java/com/boydti/fawe/bukkit/favs/Favs.java +++ b/favs/src/main/java/com/boydti/fawe/bukkit/favs/Favs.java @@ -4,6 +4,7 @@ import com.boydti.fawe.Fawe; import com.boydti.fawe.bukkit.BukkitCommand; import com.boydti.fawe.object.FaweCommand; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.Jars; import com.boydti.fawe.util.MainUtil; import com.thevoxelbox.voxelsniper.RangeBlockHelper; import com.thevoxelbox.voxelsniper.SnipeData; @@ -12,9 +13,6 @@ import com.thevoxelbox.voxelsniper.brush.perform.PerformBrush; import com.thevoxelbox.voxelsniper.command.VoxelVoxelCommand; import java.io.File; import java.io.FileOutputStream; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -40,11 +38,9 @@ public class Favs extends JavaPlugin { } if (output == null) { output = new File(this.getDataFolder().getParentFile(), "VoxelSniper.jar"); - URL worldEditUrl = new URL("https://addons-origin.cursecdn.com/files/912/511/VoxelSniper-5.171.0-SNAPSHOT.jar"); - try (ReadableByteChannel rbc = Channels.newChannel(worldEditUrl.openStream())) { - try (FileOutputStream fos = new FileOutputStream(output)) { - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - } + byte[] vsJar = Jars.VS_B_5_171_0.download(); + try (FileOutputStream fos = new FileOutputStream(output)) { + fos.write(vsJar); } } Bukkit.getPluginManager().loadPlugin(output);