From 31c5357409500e7eeb8390a4a51a1a9a7a33922d Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 14 Dec 2022 17:09:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DZIP=20bomb=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-EN.md | 4 +- README.md | 4 +- .../cn/hutool/core/compress/ZipReader.java | 29 ++++++++- .../java/cn/hutool/core/compress/ZipUtil.java | 5 +- .../core/io/stream/LimitedInputStream.java | 65 +++++++++++++++++++ 5 files changed, 99 insertions(+), 8 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java diff --git a/README-EN.md b/README-EN.md index 3b66ea7f5..20d702170 100755 --- a/README-EN.md +++ b/README-EN.md @@ -40,8 +40,8 @@

- - + +

------------------------------------------------------------------------------- diff --git a/README.md b/README.md index dbfc75c43..622c5490f 100755 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@

- - + +

------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java index 9a3d840b0..3055d8e90 100755 --- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java @@ -1,5 +1,6 @@ package cn.hutool.core.compress; +import cn.hutool.core.exceptions.ValidateException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; @@ -25,6 +26,9 @@ import java.util.zip.ZipInputStream; */ public class ZipReader implements Closeable { + // size of uncompressed zip entry shouldn't be bigger of compressed in MAX_SIZE_DIFF times + private static final int MAX_SIZE_DIFF = 100; + private ZipFile zipFile; private ZipInputStream in; @@ -202,7 +206,7 @@ public class ZipReader implements Closeable { private void readFromZipFile(final Consumer consumer) { final Enumeration em = zipFile.entries(); while (em.hasMoreElements()) { - consumer.accept(em.nextElement()); + consumer.accept(checkZipBomb(em.nextElement())); } } @@ -215,11 +219,32 @@ public class ZipReader implements Closeable { private void readFromStream(final Consumer consumer) throws IORuntimeException { try { ZipEntry zipEntry; - while (null != (zipEntry = in.getNextEntry())) { + while (null != (zipEntry = checkZipBomb(in.getNextEntry()))) { consumer.accept(zipEntry); } } catch (final IOException e) { throw new IORuntimeException(e); } } + + /** + * 检查Zip bomb漏洞 + * + * @param entry {@link ZipEntry} + * @return 检查后的{@link ZipEntry} + */ + private static ZipEntry checkZipBomb(final ZipEntry entry) { + if (null == entry) { + return null; + } + final long compressedSize = entry.getCompressedSize(); + final long uncompressedSize = entry.getSize(); + if (compressedSize < 0 || uncompressedSize < 0 || + // 默认压缩比例是100倍,一旦发现压缩率超过这个阈值,被认为是Zip bomb + compressedSize * MAX_SIZE_DIFF < uncompressedSize) { + throw new ValidateException("Zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}", + compressedSize, uncompressedSize, entry.getName()); + } + return entry; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java index 8eaa4222d..46999dd52 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipUtil.java @@ -2,13 +2,14 @@ package cn.hutool.core.compress; import cn.hutool.core.collection.iter.EnumerationIter; import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.file.FileSystemUtil; import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.LimitedInputStream; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -77,7 +78,7 @@ public class ZipUtil { */ public static InputStream getStream(final ZipFile zipFile, final ZipEntry zipEntry) { try { - return zipFile.getInputStream(zipEntry); + return new LimitedInputStream(zipFile.getInputStream(zipEntry), zipEntry.getSize()); } catch (final IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java new file mode 100755 index 000000000..d2149358f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/LimitedInputStream.java @@ -0,0 +1,65 @@ +package cn.hutool.core.io.stream; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * 限制读取最大长度的{@link FilterInputStream} 实现
+ * 来自:https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/LimitedInputStream.java + * + * @author jadx + */ +public class LimitedInputStream extends FilterInputStream { + + private final long maxSize; + private long currentPos; + + /** + * 构造 + * + * @param in {@link InputStream} + * @param maxSize 限制最大读取量,单位byte + */ + public LimitedInputStream(final InputStream in, final long maxSize) { + super(in); + this.maxSize = maxSize; + } + + @Override + public int read() throws IOException { + final int data = super.read(); + if (data != -1) { + currentPos++; + checkPos(); + } + return data; + } + + @SuppressWarnings("NullableProblems") + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + final int count = super.read(b, off, len); + if (count > 0) { + currentPos += count; + checkPos(); + } + return count; + } + + @Override + public long skip(final long n) throws IOException { + final long skipped = super.skip(n); + if (skipped != 0) { + currentPos += skipped; + checkPos(); + } + return skipped; + } + + private void checkPos() { + if (currentPos > maxSize) { + throw new IllegalStateException("Read limit exceeded"); + } + } +}