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.
This commit is contained in:
xnrand 2017-07-30 15:21:27 +02:00
parent 531ff8f4a2
commit e20f03d72e
3 changed files with 75 additions and 16 deletions

View File

@ -1,12 +1,10 @@
package com.boydti.fawe.bukkit; package com.boydti.fawe.bukkit;
import com.boydti.fawe.Fawe; import com.boydti.fawe.Fawe;
import com.boydti.fawe.util.Jars;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.lang.reflect.Field; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -57,11 +55,9 @@ public class BukkitMain extends JavaPlugin {
if (Bukkit.getPluginManager().getPlugin("WorldEdit") == null) { if (Bukkit.getPluginManager().getPlugin("WorldEdit") == null) {
try { try {
File output = new File(this.getDataFolder().getParentFile(), "WorldEdit.jar"); 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"); byte[] weJar = Jars.WE_B_6_1_7_2.download();
try (ReadableByteChannel rbc = Channels.newChannel(worldEditUrl.openStream())) { try (FileOutputStream fos = new FileOutputStream(output)) {
try (FileOutputStream fos = new FileOutputStream(output)) { fos.write(weJar);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
} }
Bukkit.getPluginManager().loadPlugin(output); Bukkit.getPluginManager().loadPlugin(output);
} catch (Throwable e) { } catch (Throwable e) {

View File

@ -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");
}
}
}

View File

@ -4,6 +4,7 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.bukkit.BukkitCommand; import com.boydti.fawe.bukkit.BukkitCommand;
import com.boydti.fawe.object.FaweCommand; import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.util.Jars;
import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MainUtil;
import com.thevoxelbox.voxelsniper.RangeBlockHelper; import com.thevoxelbox.voxelsniper.RangeBlockHelper;
import com.thevoxelbox.voxelsniper.SnipeData; import com.thevoxelbox.voxelsniper.SnipeData;
@ -12,9 +13,6 @@ import com.thevoxelbox.voxelsniper.brush.perform.PerformBrush;
import com.thevoxelbox.voxelsniper.command.VoxelVoxelCommand; import com.thevoxelbox.voxelsniper.command.VoxelVoxelCommand;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; 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.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -40,11 +38,9 @@ public class Favs extends JavaPlugin {
} }
if (output == null) { if (output == null) {
output = new File(this.getDataFolder().getParentFile(), "VoxelSniper.jar"); 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"); byte[] vsJar = Jars.VS_B_5_171_0.download();
try (ReadableByteChannel rbc = Channels.newChannel(worldEditUrl.openStream())) { try (FileOutputStream fos = new FileOutputStream(output)) {
try (FileOutputStream fos = new FileOutputStream(output)) { fos.write(vsJar);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
} }
} }
Bukkit.getPluginManager().loadPlugin(output); Bukkit.getPluginManager().loadPlugin(output);