feat: configurable image hosts (#2243)

This commit is contained in:
Jordan 2023-05-31 16:48:45 +01:00 committed by GitHub
parent a553961c05
commit 03487718e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 13 deletions

View File

@ -671,6 +671,14 @@ public class Settings extends Config {
}) })
public int MAX_IMAGE_SIZE = 8294400; 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<String> ALLOWED_IMAGE_HOSTS = new ArrayList<>(Collections.singleton(("i.imgur.com")));
} }
public static class EXTENT { public static class EXTENT {

View File

@ -52,6 +52,7 @@ import java.io.PrintWriter;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
@ -68,7 +69,6 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -81,10 +81,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -533,6 +531,21 @@ public class MainUtil {
return readImage(new FileInputStream(file)); 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) { public static BufferedImage toRGB(BufferedImage src) {
if (src == null) { if (src == null) {
return src; return src;

View File

@ -203,6 +203,7 @@ public class ImageUtil {
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png";
} }
URL url = new URL(arg); URL url = new URL(arg);
MainUtil.checkImageHost(url.toURI());
BufferedImage img = MainUtil.readImage(url); BufferedImage img = MainUtil.readImage(url);
if (img == null) { if (img == null) {
throw new IOException("Failed to read " + url + ", please try again later"); throw new IOException("Failed to read " + url + ", please try again later");
@ -218,7 +219,7 @@ public class ImageUtil {
return MainUtil.readImage(file); return MainUtil.readImage(file);
} }
throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg))); 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())); throw new InputParseException(TextComponent.of(e.getMessage()));
} }
} }
@ -229,7 +230,9 @@ public class ImageUtil {
if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) { if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) {
arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; 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:/")) { if (arg.startsWith("file:/")) {
arg = arg.replaceFirst("file:/+", ""); arg = arg.replaceFirst("file:/+", "");

View File

@ -134,6 +134,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.util.List; import java.util.List;
@ -521,11 +522,9 @@ public class BrushCommands {
@Switch(name = 'a', desc = "Use image Alpha") boolean alpha, @Switch(name = 'a', desc = "Use image Alpha") boolean alpha,
@Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut @Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut
) )
throws WorldEditException, IOException { throws WorldEditException, IOException, URISyntaxException {
URL url = new URL(imageURL); URL url = new URL(imageURL);
if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { MainUtil.checkImageHost(url.toURI());
throw new IOException("Only i.imgur.com links are allowed!");
}
BufferedImage image = MainUtil.readImage(url); BufferedImage image = MainUtil.readImage(url);
worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(radius);
if (yscale != 1) { if (yscale != 1) {

View File

@ -65,6 +65,7 @@ import org.jetbrains.annotations.Range;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -580,12 +581,10 @@ public class GenerationCommands {
@Arg(desc = "boolean", def = "true") boolean randomize, @Arg(desc = "boolean", def = "true") boolean randomize,
@Arg(desc = "TODO", def = "100") int threshold, @Arg(desc = "TODO", def = "100") int threshold,
@Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions @Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions
) throws WorldEditException, IOException { ) throws WorldEditException, IOException, URISyntaxException {
TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold); TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold);
URL url = new URL(imageURL); URL url = new URL(imageURL);
if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { MainUtil.checkImageHost(url.toURI());
throw new IOException("Only i.imgur.com links are allowed!");
}
if (dimensions != null) { if (dimensions != null) {
checkCommandArgument( checkCommandArgument(
(long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE, (long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE,