From 03487718e7a293fe93457dba7ae1d2d25230406d Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 31 May 2023 16:48:45 +0100 Subject: [PATCH] feat: configurable image hosts (#2243) --- .../core/configuration/Settings.java | 8 ++++++++ .../core/util/MainUtil.java | 19 ++++++++++++++++--- .../core/util/image/ImageUtil.java | 7 +++++-- .../worldedit/command/BrushCommands.java | 7 +++---- .../worldedit/command/GenerationCommands.java | 7 +++---- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 50b515257..0c895adf9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -671,6 +671,14 @@ public class Settings extends Config { }) public int MAX_IMAGE_SIZE = 8294400; + @Comment({ + "Whitelist of hostnames to allow images to be downloaded from", + " - Adding '*' to the list will allow any host, but this is NOT adviseable", + " - Crash exploits exist with malformed images", + " - See: https://medium.com/chargebee-engineering/perils-of-parsing-pixel-flood-attack-on-java-imageio-a97aeb06637d" + }) + public List ALLOWED_IMAGE_HOSTS = new ArrayList<>(Collections.singleton(("i.imgur.com"))); + } public static class EXTENT { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java index 8deb59d13..8122a840c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java @@ -52,6 +52,7 @@ import java.io.PrintWriter; import java.lang.reflect.Array; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -68,7 +69,6 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -81,10 +81,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.regex.Pattern; -import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -533,6 +531,21 @@ public class MainUtil { return readImage(new FileInputStream(file)); } + public static void checkImageHost(URI uri) throws IOException { + if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.contains("*")) { + return; + } + String host = uri.getHost(); + if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.stream().anyMatch(host::equalsIgnoreCase)) { + return; + } + throw new IOException(String.format( + "Host `%s` not allowed! Whitelisted image hosts are: %s", + host, + StringMan.join(Settings.settings().WEB.ALLOWED_IMAGE_HOSTS, ", ") + )); + } + public static BufferedImage toRGB(BufferedImage src) { if (src == null) { return src; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java index a8998e1f0..f99a17e6f 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java @@ -203,6 +203,7 @@ public class ImageUtil { arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; } URL url = new URL(arg); + MainUtil.checkImageHost(url.toURI()); BufferedImage img = MainUtil.readImage(url); if (img == null) { throw new IOException("Failed to read " + url + ", please try again later"); @@ -218,7 +219,7 @@ public class ImageUtil { return MainUtil.readImage(file); } throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg))); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw new InputParseException(TextComponent.of(e.getMessage())); } } @@ -229,7 +230,9 @@ public class ImageUtil { if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) { arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; } - return new URL(arg).toURI(); + URI uri = new URI(arg); + MainUtil.checkImageHost(uri); + return uri; } if (arg.startsWith("file:/")) { arg = arg.replaceFirst("file:/+", ""); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index d378decb1..358f75413 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -134,6 +134,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileSystems; import java.util.List; @@ -521,11 +522,9 @@ public class BrushCommands { @Switch(name = 'a', desc = "Use image Alpha") boolean alpha, @Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut ) - throws WorldEditException, IOException { + throws WorldEditException, IOException, URISyntaxException { URL url = new URL(imageURL); - if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { - throw new IOException("Only i.imgur.com links are allowed!"); - } + MainUtil.checkImageHost(url.toURI()); BufferedImage image = MainUtil.readImage(url); worldEdit.checkMaxBrushRadius(radius); if (yscale != 1) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index a1e6034f5..ca67ad132 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -65,6 +65,7 @@ import org.jetbrains.annotations.Range; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.concurrent.ExecutorService; @@ -580,12 +581,10 @@ public class GenerationCommands { @Arg(desc = "boolean", def = "true") boolean randomize, @Arg(desc = "TODO", def = "100") int threshold, @Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions - ) throws WorldEditException, IOException { + ) throws WorldEditException, IOException, URISyntaxException { TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold); URL url = new URL(imageURL); - if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { - throw new IOException("Only i.imgur.com links are allowed!"); - } + MainUtil.checkImageHost(url.toURI()); if (dimensions != null) { checkCommandArgument( (long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE,